Example #1
0
    def exportm3u(self, hostport, path='', add_ts=False, empty_header=False, archive=False,
                     process_url=True, header=None, fmt=None, _bytearray=bytearray):
        '''
        Exports m3u playlist
        '''
        if add_ts: hostport = 'ts://%s' % hostport  # Adding ts:// after http:// for some players

        if header is None: itemlist = self.m3uheader if not empty_header else self.m3uemptyheader
        else: itemlist = header

        items = self.sort(self.itemlist) if self.sort else self.itemlist
        for i in items:
           item = i.copy() # {'group': XXX, 'tvg': XXX, 'logo': XXX, 'name': XXX, 'tvgid': XXX, 'url': XXX}
           name = quote(ensure_str(item.get('name').replace('"', "'").replace(',', '.')), '')
           url = item['url']
           if process_url:
              if url.startswith(('http://', 'https://')) and url.endswith(('.acelive', '.acestream', '.acemedia', '.torrent')): # For .acelive and .torrent
                 item['url'] = 'http://%s/url/%s/%s.ts' % (hostport, quote(url,''), name)
              elif url.startswith('infohash://'): # For INFOHASHes
                 item['url'] = 'http://%s/infohash/%s/%s.ts' % (hostport, url.split('/')[2], name)
              elif url.startswith('acestream://'): # For PIDs
                 item['url'] = 'http://%s/content_id/%s/%s.ts' % (hostport, url.split('/')[2], name)
              elif archive and url.isdigit(): # For archive channel id's
                 item['url'] = 'http://%s/archive/play?id=%s' % (hostport, url)
              elif not archive and url.isdigit(): # For channel id's
                 item['url'] = 'http://%s/channels/play?id=%s' % (hostport, url)
              elif path.endswith('channel'): # For plugins  channel name maping
                 item['url'] = 'http://%s%s/%s' % (hostport, path, url)

           if fmt: item['url'] += '&fmt=%s' % fmt if '?' in item['url'] else '/?fmt=%s' % fmt

           itemlist += self.m3uchanneltemplate.format(**item) #Generates EXTINF line with url

        return _bytearray(itemlist, 'utf-8')
Example #2
0
        def line_generator(item):
            '''
            Generates EXTINF line with url
            '''
            item = item.copy() # {'group': XXX, 'tvg': XXX, 'logo': XXX, 'name': XXX, 'tvgid': XXX, 'url': XXX}
            params.update({'name': quote(ensure_str(item.get('name').replace('"', "'").replace(',', '.')), '')})
            url = item['url']
            if not params.get('parse_url'):
               if params.get('path') and params.get('path').endswith('channel'): # For plugins channel name maping
                  params.update({'value': url})
                  item['url'] = urlunparse(u'{schema};{netloc};{path}/{value}.{ext};;{query};'.format(**params).split(';'))
               elif url.startswith(('http://', 'https://')) and url.endswith(('.acelive', '.acestream', '.acemedia', '.torrent')): # For .acelive and .torrent
                  params.update({'value': quote(url,'')})
                  item['url'] = urlunparse(u'{schema};{netloc};/url/{value}/{name}.{ext};;{query};'.format(**params).split(';'))
               elif url.startswith('infohash://'): # For INFOHASHes
                  params.update({'value': url.split('/')[2]})
                  item['url'] = urlunparse(u'{schema};{netloc};/infohash/{value}/{name}.{ext};;{query};'.format(**params).split(';'))
               elif url.startswith('acestream://'): # For PIDs
                  params.update({'value': url.split('/')[2]})
                  item['url'] = urlunparse(u'{schema};{netloc};/content_id/{value}/{name}.{ext};;{query};'.format(**params).split(';'))
               elif params.get('archive') and url.isdigit(): # For archive channel id's
                  params.update({'value': url})
                  item['url'] = urlunparse(u'{schema};{netloc};/archive/play;;id={value};'.format(**params).split(';'))
               elif not params.get('archive') and url.isdigit(): # For channel id's
                  params.update({'value': url})
                  item['url'] = urlunparse(u'{schema};{netloc};/channels/play;;id={value};'.format(**params).split(';'))

            return self.m3uchanneltemplate.format(**item)
Example #3
0
    def Playlistparser(self):
        try:
            s = requests.Session()
            s.mount('file://', FileAdapter())
            with s.get(config.url,
                       headers=self.headers,
                       proxies=config.proxies,
                       stream=False,
                       timeout=30) as playlist:
                if playlist.status_code != 304:
                    if playlist.encoding is None: playlist.encoding = 'utf-8'
                    playlist = playlist.json()
                    self.headers['If-Modified-Since'] = gevent.time.strftime(
                        '%a, %d %b %Y %H:%M:%S %Z',
                        gevent.time.gmtime(self.playlisttime))
                    self.playlist = PlaylistGenerator(
                        m3uchanneltemplate=config.m3uchanneltemplate)
                    self.picons = picons.logomap
                    self.channels = {}
                    m = requests.auth.hashlib.md5()
                    logging.info('[%s]: playlist %s downloaded' %
                                 (self.__class__.__name__, config.url))
                    try:
                        urlpattern = requests.utils.re.compile(
                            r'^(acestream|infohash)://[0-9a-f]{40}$|^(http|https)://.*.(acelive|acestream|acemedia|torrent)$'
                        )
                        for channel in playlist['channels']:
                            name = channel['name']
                            url = 'infohash://{url}'.format(**channel)
                            channel['group'] = channel.pop('cat')
                            channel['logo'] = self.picons[name] = channel.get(
                                'logo', picons.logomap.get(name))
                            channel['tvgid'] = channel.pop('program')

                            if requests.utils.re.search(urlpattern, url):
                                self.channels[name] = url
                                channel['url'] = quote(ensure_str(name), '')

                            self.playlist.addItem(channel)
                            m.update(ensure_binary(name))

                    except Exception as e:
                        logging.error("[%s]: can't parse JSON! %s" %
                                      (self.__class__.__name__, repr(e)))
                        return False

                    self.etag = '"' + m.hexdigest() + '"'
                    logging.debug('[%s]: plugin playlist generated' %
                                  self.__class__.__name__)

                self.playlisttime = gevent.time.time()

        except requests.exceptions.RequestException:
            logging.error("[%s]: can't download %s playlist!" %
                          (self.__class__.__name__, config.url))
            return False
        except:
            logging.error(traceback.format_exc())
            return False
    def Playlistparser(self):
        try:
            s = requests.Session()
            s.mount('file://', FileAdapter())
            with s.get(config.url,
                       headers=self.headers,
                       proxies=config.proxies,
                       stream=False,
                       timeout=30) as r:
                if r.status_code != 304:
                    if r.encoding is None: r.encoding = 'utf-8'
                    self.headers['If-Modified-Since'] = gevent.time.strftime(
                        '%a, %d %b %Y %H:%M:%S %Z',
                        gevent.time.gmtime(self.playlisttime))
                    self.playlist = PlaylistGenerator(
                        m3uchanneltemplate=config.m3uchanneltemplate)
                    self.picons = picons.logomap
                    self.channels = {}
                    m = requests.auth.hashlib.md5()
                    logging.info('[%s]: playlist %s downloaded' %
                                 (self.__class__.__name__, config.url))
                    pattern = requests.utils.re.compile(
                        r',(?P<name>.+) \((?P<group>.+)\)[\r\n]+(?P<url>[^\r\n]+)?'
                    )
                    urlpattern = requests.utils.re.compile(
                        r'^(acestream|infohash)://[0-9a-f]{40}$|^(http|https)://.*.(acelive|acestream|acemedia|torrent)$'
                    )
                    for match in pattern.finditer(r.text,
                                                  requests.auth.re.MULTILINE):
                        itemdict = match.groupdict()
                        name = itemdict.get('name', '')
                        itemdict['logo'] = self.picons[name] = itemdict.get(
                            'logo', picons.logomap.get(name))
                        url = itemdict['url']

                        if requests.utils.re.search(urlpattern, url):
                            self.channels[name] = url
                            itemdict['url'] = quote(ensure_str(name), '')

                        self.playlist.addItem(itemdict)
                        m.update(ensure_binary(name))

                    self.etag = '"' + m.hexdigest() + '"'
                    logging.debug('[%s]: plugin playlist generated' %
                                  self.__class__.__name__)

                self.playlisttime = gevent.time.time()

        except requests.exceptions.RequestException:
            logging.error("[%s]: can't download %s playlist!" %
                          (self.__class__.__name__, config.url))
            return False
        except:
            logging.error(traceback.format_exc())
            return False

        return True
Example #5
0
    def Playlistparser(self):
        try:
            with requests.get(config.url,
                              headers=self.headers,
                              proxies=config.proxies,
                              stream=False,
                              timeout=30) as playlist:
                if playlist.status_code != 304:
                    if playlist.encoding is None: playlist.encoding = 'utf-8'
                    self.headers['If-Modified-Since'] = gevent.time.strftime(
                        '%a, %d %b %Y %H:%M:%S %Z',
                        gevent.time.gmtime(self.playlisttime))
                    self.playlist = PlaylistGenerator(
                        m3uchanneltemplate=config.m3uchanneltemplate)
                    self.picons = picons.logomap
                    self.channels = {}
                    m = requests.auth.hashlib.md5()
                    self.logger.info('Playlist %s downloaded' % config.url)
                    try:
                        for channel in playlist.json()['channels']:
                            channel['name'] = name = channel.get('name', '')
                            channel[
                                'url'] = url = 'acestream://%s' % channel.get(
                                    'url')
                            channel['group'] = channel.get('cat')
                            if not 'logo' in channel:
                                channel['logo'] = picons.logomap.get(name)
                            self.picons[name] = channel['logo']

                            if url.startswith(('acestream://', 'infohash://')) \
                                  or (url.startswith(('http://','https://')) and url.endswith(('.acelive','.acestream','.acemedia'))):
                                self.channels[name] = url
                                channel['url'] = quote(
                                    ensure_str(name) + '.ts', '')

                            self.playlist.addItem(channel)
                            m.update(name.encode('utf-8'))

                    except Exception as e:
                        self.logger.error("Can't parse JSON! %s" % repr(e))
                        return

                    self.etag = '"' + m.hexdigest() + '"'
                    self.logger.debug('torrent-telik.m3u playlist generated')

                self.playlisttime = gevent.time.time()

        except requests.exceptions.RequestException:
            self.logger.error("Can't download %s playlist!" % config.url)
            return False
        except:
            self.logger.error(traceback.format_exc())
            return False

        return True
Example #6
0
    def Playlistparser(self):
        try:
            s = requests.Session()
            s.mount('file://', FileAdapter())
            with s.get(config.url,
                       headers=self.headers,
                       proxies=config.proxies,
                       stream=False,
                       timeout=30) as r:
                if r.status_code != 304:
                    if r.encoding is None: r.encoding = 'utf-8'
                    self.headers['If-Modified-Since'] = gevent.time.strftime(
                        '%a, %d %b %Y %H:%M:%S %Z',
                        gevent.time.gmtime(self.playlisttime))
                    self.playlist = PlaylistGenerator(
                        m3uchanneltemplate=config.m3uchanneltemplate)
                    self.picons = picons.logomap
                    self.channels = {}
                    m = requests.auth.hashlib.md5()
                    self.logger.info('Playlist %s downloaded' % config.url)
                    pattern = requests.auth.re.compile(
                        r',(?P<name>.+) \((?P<group>.+)\)[\r\n]+(?P<url>[^\r\n]+)?'
                    )
                    for match in pattern.finditer(r.text,
                                                  requests.auth.re.MULTILINE):
                        itemdict = match.groupdict()
                        name = itemdict.get('name', '')
                        if not 'logo' in itemdict:
                            itemdict['logo'] = picons.logomap.get(name)
                        self.picons[name] = itemdict['logo']

                        url = itemdict['url']
                        if url.startswith(('acestream://', 'infohash://')) \
                             or (url.startswith(('http://','https://')) and url.endswith(('.acelive', '.acestream', '.acemedia', '.torrent'))):
                            self.channels[name] = url
                            itemdict['url'] = quote(ensure_str('%s.ts' % name),
                                                    '')

                        self.playlist.addItem(itemdict)
                        m.update(name.encode('utf-8'))

                    self.etag = '"' + m.hexdigest() + '"'
                    self.logger.debug('torrenttv.m3u playlist generated')

                self.playlisttime = gevent.time.time()

        except requests.exceptions.RequestException:
            self.logger.error("Can't download %s playlist!" % config.url)
            return False
        except:
            self.logger.error(traceback.format_exc())
            return False

        return True
Example #7
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
Example #8
0
 def echo_params(self, request):
     params = sorted([(ensure_str(k), ensure_str(v))
                      for k, v in request.params.items()])
     return Response(repr(params))
Example #9
0
    async def urlopen(self,
                      method,
                      url,
                      body=None,
                      headers=None,
                      retries=None,
                      timeout=_Default,
                      pool_timeout=None,
                      body_pos=None,
                      **response_kw):
        """
        Get a connection from the pool and perform an HTTP request. This is the
        lowest level call for making a request, so you'll need to specify all
        the raw details.

        .. note::

           More commonly, it's appropriate to use a convenience method provided
           by :class:`.RequestMethods`, such as :meth:`request`.

        :param method:
            HTTP request method (such as GET, POST, PUT, etc.)

        :param body:
            Data to send in the request body (useful for creating
            POST requests, see HTTPConnectionPool.post_url for
            more convenience).

        :param headers:
            Dictionary of custom headers to send, such as User-Agent,
            If-None-Match, etc. If None, pool headers are used. If provided,
            these headers completely replace any pool-specific headers.

        :param retries:
            Configure the number of retries to allow before raising a
            :class:`~urllib3.exceptions.MaxRetryError` exception.

            Pass ``None`` to retry until you receive a response. Pass a
            :class:`~urllib3.util.retry.Retry` object for fine-grained control
            over different types of retries.
            Pass an integer number to retry connection errors that many times,
            but no other types of errors. Pass zero to never retry.

            If ``False``, then retries are disabled and any exception is raised
            immediately. Also, instead of raising a MaxRetryError on redirects,
            the redirect response will be returned.

        :type retries: :class:`~urllib3.util.retry.Retry`, False, or an int.

        :param timeout:
            If specified, overrides the default timeout for this one
            request. It may be a float (in seconds) or an instance of
            :class:`urllib3.util.Timeout`.

        :param pool_timeout:
            If set and the pool is set to block=True, then this method will
            block for ``pool_timeout`` seconds and raise EmptyPoolError if no
            connection is available within the time period.

        :param int body_pos:
            Position to seek to in file-like body in the event of a retry or
            redirect. Typically this won't need to be set because urllib3 will
            auto-populate the value when needed.

        :param \\**response_kw:
            Additional parameters are passed to
            :meth:`urllib3.response.HTTPResponse.from_base`
        """
        if headers is None:
            headers = self.headers

        if not isinstance(retries, Retry):
            retries = Retry.from_int(retries,
                                     default=self.retries,
                                     redirect=False)

        # Ensure that the URL we're connecting to is properly encoded
        if url.startswith("/"):
            url = six.ensure_str(_encode_target(url))
        else:
            url = six.ensure_str(parse_url(url).url)

        conn = None

        # Track whether `conn` needs to be released before
        # returning/raising/recursing.
        release_this_conn = False

        # Merge the proxy headers. Only do this in HTTP. We have to copy the
        # headers dict so we can safely change it without those changes being
        # reflected in anyone else's copy.
        if self.scheme == "http":
            headers = headers.copy()
            headers.update(self.proxy_headers)

        # Must keep the exception bound to a separate variable or else Python 3
        # complains about UnboundLocalError.
        err = None

        # Keep track of whether we cleanly exited the except block. This
        # ensures we do proper cleanup in finally.
        clean_exit = False

        # Rewind body position, if needed. Record current position
        # for future rewinds in the event of a redirect/retry.
        body_pos = set_file_position(body, body_pos)

        if body is not None:
            _add_transport_headers(headers)

        try:
            # Request a connection from the queue.
            timeout_obj = self._get_timeout(timeout)
            conn = await self._get_conn(timeout=pool_timeout)

            conn.timeout = timeout_obj.connect_timeout

            # Make the request on the base connection object.
            base_response = await self._make_request(conn,
                                                     method,
                                                     url,
                                                     timeout=timeout_obj,
                                                     body=body,
                                                     headers=headers)

            # Pass method to Response for length checking
            response_kw["request_method"] = method

            # Import httplib's response into our own wrapper object
            response = self.ResponseCls.from_base(base_response,
                                                  pool=self,
                                                  retries=retries,
                                                  **response_kw)

            # Everything went great!
            clean_exit = True

        except queue.Empty:
            # Timed out by queue.
            raise EmptyPoolError(self, "No pool connections are available.")

        except (
                TimeoutError,
                SocketError,
                ProtocolError,
                h11.ProtocolError,
                BaseSSLError,
                SSLError,
                CertificateError,
        ) as e:
            # Discard the connection for these exceptions. It will be
            # replaced during the next _get_conn() call.
            clean_exit = False

            if isinstance(e, (BaseSSLError, CertificateError)):
                e = SSLError(e)
            elif isinstance(e,
                            (SocketError, NewConnectionError)) and self.proxy:
                e = ProxyError("Cannot connect to proxy.", e)
            elif isinstance(e, (SocketError, h11.ProtocolError)):
                e = ProtocolError("Connection aborted.", e)

            retries = retries.increment(method,
                                        url,
                                        error=e,
                                        _pool=self,
                                        _stacktrace=sys.exc_info()[2])
            retries.sleep()

            # Keep track of the error for the retry warning.
            err = e

        finally:
            if not clean_exit:
                # We hit some kind of exception, handled or otherwise. We need
                # to throw the connection away unless explicitly told not to.
                # Close the connection, set the variable to None, and make sure
                # we put the None back in the pool to avoid leaking it.
                conn = conn and conn.close()
                release_this_conn = True

            if release_this_conn:
                # Put the connection back to be reused. If the connection is
                # expired then it will be None, which will get replaced with a
                # fresh connection during _get_conn.
                self._put_conn(conn)

        if not conn:
            # Try again
            log.warning("Retrying (%r) after connection broken by '%r': %s",
                        retries, err, url)
            return await self.urlopen(method,
                                      url,
                                      body,
                                      headers,
                                      retries,
                                      timeout=timeout,
                                      pool_timeout=pool_timeout,
                                      body_pos=body_pos,
                                      **response_kw)

        async def drain_and_release_conn(response):
            try:
                # discard any remaining response body, the connection will be
                # released back to the pool once the entire response is read
                await response.read()
            except (TimeoutError, SocketError, ProtocolError, BaseSSLError,
                    SSLError):
                pass

        # Check if we should retry the HTTP response.
        has_retry_after = bool(response.getheader("Retry-After"))
        if retries.is_retry(method, response.status, has_retry_after):
            try:
                retries = retries.increment(method,
                                            url,
                                            response=response,
                                            _pool=self)
            except MaxRetryError:
                if retries.raise_on_status:
                    # Drain and release the connection for this response, since
                    # we're not returning it to be released manually.
                    await drain_and_release_conn(response)
                    raise
                return response

            # drain and return the connection to the pool before recursing
            await drain_and_release_conn(response)

            retries.sleep(response)
            log.debug("Retry: %s", url)
            return await self.urlopen(method,
                                      url,
                                      body,
                                      headers,
                                      retries=retries,
                                      timeout=timeout,
                                      pool_timeout=pool_timeout,
                                      body_pos=body_pos,
                                      **response_kw)

        return response