mirror of
https://github.com/iv-org/invidious.git
synced 2025-04-27 15:30:35 +05:30
Connection pool: ensure response is fully read
The streaming API of HTTP::Client has an internal buffer that will continue to persist onto the next request unless the response is fully read. This commit privatizes the #client method of Pool and instead expose various HTTP request methods that will call and yield the underlying request and response. This way, we can ensure that the resposne is fully read before the client is passed back into the pool for another request.
This commit is contained in:
parent
4ce0f775fb
commit
e9b2db4d80
@ -166,7 +166,7 @@ def fetch_channel(ucid, pull_all_videos : Bool)
|
|||||||
}
|
}
|
||||||
|
|
||||||
LOGGER.trace("fetch_channel: #{ucid} : Downloading RSS feed")
|
LOGGER.trace("fetch_channel: #{ucid} : Downloading RSS feed")
|
||||||
rss = YT_POOL.client &.get("/feeds/videos.xml?channel_id=#{ucid}").body
|
rss = YT_POOL.get("/feeds/videos.xml?channel_id=#{ucid}").body
|
||||||
LOGGER.trace("fetch_channel: #{ucid} : Parsing RSS feed")
|
LOGGER.trace("fetch_channel: #{ucid} : Parsing RSS feed")
|
||||||
rss = XML.parse(rss)
|
rss = XML.parse(rss)
|
||||||
|
|
||||||
|
@ -33,8 +33,33 @@ module Invidious::ConnectionPool
|
|||||||
# Returns the underlying `DB::Pool` object
|
# Returns the underlying `DB::Pool` object
|
||||||
abstract def pool : DB::Pool(PoolClient)
|
abstract def pool : DB::Pool(PoolClient)
|
||||||
|
|
||||||
# Checks out a client from the pool
|
{% for method in %w[get post put patch delete head options] %}
|
||||||
def client(&)
|
def {{method.id}}(*args, **kwargs)
|
||||||
|
self.client do | client |
|
||||||
|
client.{{method.id}}(*args, **kwargs) do | response |
|
||||||
|
|
||||||
|
result = yield response
|
||||||
|
return result
|
||||||
|
|
||||||
|
ensure
|
||||||
|
response.body_io?.try &. skip_to_end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def {{method.id}}(*args, **kwargs)
|
||||||
|
self.client do | client |
|
||||||
|
return response = client.{{method.id}}(*args, **kwargs)
|
||||||
|
ensure
|
||||||
|
if response
|
||||||
|
response.body_io?.try &. skip_to_end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
{% end %}
|
||||||
|
|
||||||
|
# Checks out a client in the pool
|
||||||
|
private def client(&)
|
||||||
pool.checkout do |http_client|
|
pool.checkout do |http_client|
|
||||||
# Proxy needs to be reinstated every time we get a client from the pool
|
# Proxy needs to be reinstated every time we get a client from the pool
|
||||||
http_client.proxy = make_configured_http_proxy_client() if CONFIG.http_proxy
|
http_client.proxy = make_configured_http_proxy_client() if CONFIG.http_proxy
|
||||||
|
@ -26,7 +26,7 @@ def fetch_mix(rdid, video_id, cookies = nil, locale = nil)
|
|||||||
end
|
end
|
||||||
|
|
||||||
video_id = "CvFH_6DNRCY" if rdid.starts_with? "OLAK5uy_"
|
video_id = "CvFH_6DNRCY" if rdid.starts_with? "OLAK5uy_"
|
||||||
response = YT_POOL.client &.get("/watch?v=#{video_id}&list=#{rdid}&gl=US&hl=en", headers)
|
response = YT_POOL.get("/watch?v=#{video_id}&list=#{rdid}&gl=US&hl=en", headers)
|
||||||
initial_data = extract_initial_data(response.body)
|
initial_data = extract_initial_data(response.body)
|
||||||
|
|
||||||
if !initial_data["contents"]["twoColumnWatchNextResults"]["playlist"]?
|
if !initial_data["contents"]["twoColumnWatchNextResults"]["playlist"]?
|
||||||
|
@ -26,7 +26,7 @@ module Invidious::Routes::API::Manifest
|
|||||||
end
|
end
|
||||||
|
|
||||||
if dashmpd = video.dash_manifest_url
|
if dashmpd = video.dash_manifest_url
|
||||||
response = YT_POOL.client &.get(URI.parse(dashmpd).request_target)
|
response = YT_POOL.get(URI.parse(dashmpd).request_target)
|
||||||
|
|
||||||
if response.status_code != 200
|
if response.status_code != 200
|
||||||
haltf env, status_code: response.status_code
|
haltf env, status_code: response.status_code
|
||||||
@ -167,7 +167,7 @@ module Invidious::Routes::API::Manifest
|
|||||||
|
|
||||||
# /api/manifest/hls_playlist/*
|
# /api/manifest/hls_playlist/*
|
||||||
def self.get_hls_playlist(env)
|
def self.get_hls_playlist(env)
|
||||||
response = YT_POOL.client &.get(env.request.path)
|
response = YT_POOL.get(env.request.path)
|
||||||
|
|
||||||
if response.status_code != 200
|
if response.status_code != 200
|
||||||
haltf env, status_code: response.status_code
|
haltf env, status_code: response.status_code
|
||||||
@ -223,7 +223,7 @@ module Invidious::Routes::API::Manifest
|
|||||||
|
|
||||||
# /api/manifest/hls_variant/*
|
# /api/manifest/hls_variant/*
|
||||||
def self.get_hls_variant(env)
|
def self.get_hls_variant(env)
|
||||||
response = YT_POOL.client &.get(env.request.path)
|
response = YT_POOL.get(env.request.path)
|
||||||
|
|
||||||
if response.status_code != 200
|
if response.status_code != 200
|
||||||
haltf env, status_code: response.status_code
|
haltf env, status_code: response.status_code
|
||||||
|
@ -106,7 +106,7 @@ module Invidious::Routes::API::V1::Videos
|
|||||||
# Auto-generated captions often have cues that aren't aligned properly with the video,
|
# Auto-generated captions often have cues that aren't aligned properly with the video,
|
||||||
# as well as some other markup that makes it cumbersome, so we try to fix that here
|
# as well as some other markup that makes it cumbersome, so we try to fix that here
|
||||||
if caption.name.includes? "auto-generated"
|
if caption.name.includes? "auto-generated"
|
||||||
caption_xml = YT_POOL.client &.get(url).body
|
caption_xml = YT_POOL.get(url).body
|
||||||
|
|
||||||
settings_field = {
|
settings_field = {
|
||||||
"Kind" => "captions",
|
"Kind" => "captions",
|
||||||
@ -147,7 +147,7 @@ module Invidious::Routes::API::V1::Videos
|
|||||||
query_params = uri.query_params
|
query_params = uri.query_params
|
||||||
query_params["fmt"] = "vtt"
|
query_params["fmt"] = "vtt"
|
||||||
uri.query_params = query_params
|
uri.query_params = query_params
|
||||||
webvtt = YT_POOL.client &.get(uri.request_target).body
|
webvtt = YT_POOL.get(uri.request_target).body
|
||||||
|
|
||||||
if webvtt.starts_with?("<?xml")
|
if webvtt.starts_with?("<?xml")
|
||||||
webvtt = caption.timedtext_to_vtt(webvtt)
|
webvtt = caption.timedtext_to_vtt(webvtt)
|
||||||
@ -300,7 +300,7 @@ module Invidious::Routes::API::V1::Videos
|
|||||||
cache_annotation(id, annotations)
|
cache_annotation(id, annotations)
|
||||||
end
|
end
|
||||||
else # "youtube"
|
else # "youtube"
|
||||||
response = YT_POOL.client &.get("/annotations_invideo?video_id=#{id}")
|
response = YT_POOL.get("/annotations_invideo?video_id=#{id}")
|
||||||
|
|
||||||
if response.status_code != 200
|
if response.status_code != 200
|
||||||
haltf env, response.status_code
|
haltf env, response.status_code
|
||||||
|
@ -392,7 +392,7 @@ module Invidious::Routes::Channels
|
|||||||
value = env.request.resource.split("/")[2]
|
value = env.request.resource.split("/")[2]
|
||||||
body = ""
|
body = ""
|
||||||
{"channel", "user", "c"}.each do |type|
|
{"channel", "user", "c"}.each do |type|
|
||||||
response = YT_POOL.client &.get("/#{type}/#{value}/live?disable_polymer=1")
|
response = YT_POOL.get("/#{type}/#{value}/live?disable_polymer=1")
|
||||||
if response.status_code == 200
|
if response.status_code == 200
|
||||||
body = response.body
|
body = response.body
|
||||||
end
|
end
|
||||||
|
@ -92,7 +92,7 @@ module Invidious::Routes::Embed
|
|||||||
|
|
||||||
return env.redirect url
|
return env.redirect url
|
||||||
when "live_stream"
|
when "live_stream"
|
||||||
response = YT_POOL.client &.get("/embed/live_stream?channel=#{env.params.query["channel"]? || ""}")
|
response = YT_POOL.get("/embed/live_stream?channel=#{env.params.query["channel"]? || ""}")
|
||||||
video_id = response.body.match(/"video_id":"(?<video_id>[a-zA-Z0-9_-]{11})"/).try &.["video_id"]
|
video_id = response.body.match(/"video_id":"(?<video_id>[a-zA-Z0-9_-]{11})"/).try &.["video_id"]
|
||||||
|
|
||||||
env.params.query.delete_all("channel")
|
env.params.query.delete_all("channel")
|
||||||
|
@ -9,10 +9,10 @@ module Invidious::Routes::ErrorRoutes
|
|||||||
item = md["id"]
|
item = md["id"]
|
||||||
|
|
||||||
# Check if item is branding URL e.g. https://youtube.com/gaming
|
# Check if item is branding URL e.g. https://youtube.com/gaming
|
||||||
response = YT_POOL.client &.get("/#{item}")
|
response = YT_POOL.get("/#{item}")
|
||||||
|
|
||||||
if response.status_code == 301
|
if response.status_code == 301
|
||||||
response = YT_POOL.client &.get(URI.parse(response.headers["Location"]).request_target)
|
response = YT_POOL.get(URI.parse(response.headers["Location"]).request_target)
|
||||||
end
|
end
|
||||||
|
|
||||||
if response.body.empty?
|
if response.body.empty?
|
||||||
@ -40,7 +40,7 @@ module Invidious::Routes::ErrorRoutes
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Check if item is video ID
|
# Check if item is video ID
|
||||||
if item.match(/^[a-zA-Z0-9_-]{11}$/) && YT_POOL.client &.head("/watch?v=#{item}").status_code != 404
|
if item.match(/^[a-zA-Z0-9_-]{11}$/) && YT_POOL.head("/watch?v=#{item}").status_code != 404
|
||||||
env.response.headers["Location"] = url
|
env.response.headers["Location"] = url
|
||||||
haltf env, status_code: 302
|
haltf env, status_code: 302
|
||||||
end
|
end
|
||||||
|
@ -160,8 +160,9 @@ module Invidious::Routes::Feeds
|
|||||||
"default" => "http://www.w3.org/2005/Atom",
|
"default" => "http://www.w3.org/2005/Atom",
|
||||||
}
|
}
|
||||||
|
|
||||||
response = YT_POOL.client &.get("/feeds/videos.xml?channel_id=#{ucid}")
|
response = YT_POOL.get("/feeds/videos.xml?channel_id=#{ucid}")
|
||||||
return error_atom(404, NotFoundException.new("Channel does not exist.")) if response.status_code == 404
|
return error_atom(404, NotFoundException.new("Channel does not exist.")) if response.status_code == 404
|
||||||
|
|
||||||
rss = XML.parse(response.body)
|
rss = XML.parse(response.body)
|
||||||
|
|
||||||
videos = rss.xpath_nodes("//default:feed/default:entry", namespaces).map do |entry|
|
videos = rss.xpath_nodes("//default:feed/default:entry", namespaces).map do |entry|
|
||||||
@ -304,7 +305,7 @@ module Invidious::Routes::Feeds
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
response = YT_POOL.client &.get("/feeds/videos.xml?playlist_id=#{plid}")
|
response = YT_POOL.get("/feeds/videos.xml?playlist_id=#{plid}")
|
||||||
return error_atom(404, NotFoundException.new("Playlist does not exist.")) if response.status_code == 404
|
return error_atom(404, NotFoundException.new("Playlist does not exist.")) if response.status_code == 404
|
||||||
|
|
||||||
document = XML.parse(response.body)
|
document = XML.parse(response.body)
|
||||||
|
@ -12,7 +12,7 @@ module Invidious::Routes::Images
|
|||||||
end
|
end
|
||||||
|
|
||||||
begin
|
begin
|
||||||
GGPHT_POOL.client &.get(url, headers) do |resp|
|
GGPHT_POOL.get(url, headers) do |resp|
|
||||||
return self.proxy_image(env, resp)
|
return self.proxy_image(env, resp)
|
||||||
end
|
end
|
||||||
rescue ex
|
rescue ex
|
||||||
@ -42,7 +42,7 @@ module Invidious::Routes::Images
|
|||||||
end
|
end
|
||||||
|
|
||||||
begin
|
begin
|
||||||
Invidious::ConnectionPool.get_ytimg_pool(authority).client &.get(url, headers) do |resp|
|
Invidious::ConnectionPool.get_ytimg_pool(authority).get(url, headers) do |resp|
|
||||||
env.response.headers["Connection"] = "close"
|
env.response.headers["Connection"] = "close"
|
||||||
return self.proxy_image(env, resp)
|
return self.proxy_image(env, resp)
|
||||||
end
|
end
|
||||||
@ -65,7 +65,7 @@ module Invidious::Routes::Images
|
|||||||
end
|
end
|
||||||
|
|
||||||
begin
|
begin
|
||||||
Invidious::ConnectionPool.get_ytimg_pool("i9").client &.get(url, headers) do |resp|
|
Invidious::ConnectionPool.get_ytimg_pool("i9").get(url, headers) do |resp|
|
||||||
return self.proxy_image(env, resp)
|
return self.proxy_image(env, resp)
|
||||||
end
|
end
|
||||||
rescue ex
|
rescue ex
|
||||||
@ -81,7 +81,7 @@ module Invidious::Routes::Images
|
|||||||
end
|
end
|
||||||
|
|
||||||
begin
|
begin
|
||||||
YT_POOL.client &.get(env.request.resource, headers) do |response|
|
YT_POOL.get(env.request.resource, headers) do |response|
|
||||||
env.response.status_code = response.status_code
|
env.response.status_code = response.status_code
|
||||||
response.headers.each do |key, value|
|
response.headers.each do |key, value|
|
||||||
if !RESPONSE_HEADERS_BLACKLIST.includes?(key.downcase)
|
if !RESPONSE_HEADERS_BLACKLIST.includes?(key.downcase)
|
||||||
@ -111,7 +111,7 @@ module Invidious::Routes::Images
|
|||||||
if name == "maxres.jpg"
|
if name == "maxres.jpg"
|
||||||
build_thumbnails(id).each do |thumb|
|
build_thumbnails(id).each do |thumb|
|
||||||
thumbnail_resource_path = "/vi/#{id}/#{thumb[:url]}.jpg"
|
thumbnail_resource_path = "/vi/#{id}/#{thumb[:url]}.jpg"
|
||||||
if Invidious::ConnectionPool.get_ytimg_pool("i").client &.head(thumbnail_resource_path, headers).status_code == 200
|
if Invidious::ConnectionPool.get_ytimg_pool("i").head(thumbnail_resource_path, headers).status_code == 200
|
||||||
name = thumb[:url] + ".jpg"
|
name = thumb[:url] + ".jpg"
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
@ -127,7 +127,7 @@ module Invidious::Routes::Images
|
|||||||
end
|
end
|
||||||
|
|
||||||
begin
|
begin
|
||||||
Invidious::ConnectionPool.get_ytimg_pool("i").client &.get(url, headers) do |resp|
|
Invidious::ConnectionPool.get_ytimg_pool("i").get(url, headers) do |resp|
|
||||||
return self.proxy_image(env, resp)
|
return self.proxy_image(env, resp)
|
||||||
end
|
end
|
||||||
rescue ex
|
rescue ex
|
||||||
|
@ -464,7 +464,7 @@ module Invidious::Routes::Playlists
|
|||||||
|
|
||||||
# Undocumented, creates anonymous playlist with specified 'video_ids', max 50 videos
|
# Undocumented, creates anonymous playlist with specified 'video_ids', max 50 videos
|
||||||
def self.watch_videos(env)
|
def self.watch_videos(env)
|
||||||
response = YT_POOL.client &.get(env.request.resource)
|
response = YT_POOL.get(env.request.resource)
|
||||||
if url = response.headers["Location"]?
|
if url = response.headers["Location"]?
|
||||||
url = URI.parse(url).request_target
|
url = URI.parse(url).request_target
|
||||||
return env.redirect url
|
return env.redirect url
|
||||||
|
@ -16,11 +16,11 @@ module Invidious::Search
|
|||||||
# Search a youtube channel
|
# Search a youtube channel
|
||||||
# TODO: clean code, and rely more on YoutubeAPI
|
# TODO: clean code, and rely more on YoutubeAPI
|
||||||
def channel(query : Query) : Array(SearchItem)
|
def channel(query : Query) : Array(SearchItem)
|
||||||
response = YT_POOL.client &.get("/channel/#{query.channel}")
|
response = YT_POOL.get("/channel/#{query.channel}")
|
||||||
|
|
||||||
if response.status_code == 404
|
if response.status_code == 404
|
||||||
response = YT_POOL.client &.get("/user/#{query.channel}")
|
response = YT_POOL.get("/user/#{query.channel}")
|
||||||
response = YT_POOL.client &.get("/c/#{query.channel}") if response.status_code == 404
|
response = YT_POOL.get("/c/#{query.channel}") if response.status_code == 404
|
||||||
initial_data = extract_initial_data(response.body)
|
initial_data = extract_initial_data(response.body)
|
||||||
ucid = initial_data.dig?("header", "c4TabbedHeaderRenderer", "channelId").try(&.as_s?)
|
ucid = initial_data.dig?("header", "c4TabbedHeaderRenderer", "channelId").try(&.as_s?)
|
||||||
raise ChannelSearchException.new(query.channel) if !ucid
|
raise ChannelSearchException.new(query.channel) if !ucid
|
||||||
|
@ -639,15 +639,13 @@ module YoutubeAPI
|
|||||||
LOGGER.trace("YoutubeAPI: POST data: #{data}")
|
LOGGER.trace("YoutubeAPI: POST data: #{data}")
|
||||||
|
|
||||||
# Send the POST request
|
# Send the POST request
|
||||||
body = YT_POOL.client() do |client|
|
body = YT_POOL.post(url, headers: headers, body: data.to_json) do |response|
|
||||||
client.post(url, headers: headers, body: data.to_json) do |response|
|
if response.status_code != 200
|
||||||
if response.status_code != 200
|
raise InfoException.new("Error: non 200 status code. Youtube API returned \
|
||||||
raise InfoException.new("Error: non 200 status code. Youtube API returned \
|
status code #{response.status_code}. See <a href=\"https://docs.invidious.io/youtube-errors-explained/\"> \
|
||||||
status code #{response.status_code}. See <a href=\"https://docs.invidious.io/youtube-errors-explained/\"> \
|
https://docs.invidious.io/youtube-errors-explained/</a> for troubleshooting.")
|
||||||
https://docs.invidious.io/youtube-errors-explained/</a> for troubleshooting.")
|
|
||||||
end
|
|
||||||
self._decompress(response.body_io, response.headers["Content-Encoding"]?)
|
|
||||||
end
|
end
|
||||||
|
self._decompress(response.body_io, response.headers["Content-Encoding"]?)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Convert result to Hash
|
# Convert result to Hash
|
||||||
@ -695,7 +693,7 @@ module YoutubeAPI
|
|||||||
# Send the POST request
|
# Send the POST request
|
||||||
|
|
||||||
begin
|
begin
|
||||||
response = COMPANION_POOL.client &.post(endpoint, headers: headers, body: data.to_json)
|
response = COMPANION_POOL.post(endpoint, headers: headers, body: data.to_json)
|
||||||
body = response.body
|
body = response.body
|
||||||
if (response.status_code != 200)
|
if (response.status_code != 200)
|
||||||
raise Exception.new(
|
raise Exception.new(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user