ファイル
btrc-hub/backend/app/services/preview/http_fetcher.rb
T
2026-07-09 23:11:31 +09:00

70 行
2.3 KiB
Ruby

require 'net/http'
module Preview
class HttpFetcher
class FetchFailed < StandardError; end
class FetchTimeout < FetchFailed; end
class ResponseTooLarge < FetchFailed; end
MAX_REDIRECTS = 5
DEFAULT_MAX_BYTES = 5.megabytes
Response = Data.define(:body, :content_type, :url)
def self.fetch(raw_url, max_bytes: DEFAULT_MAX_BYTES, redirects: MAX_REDIRECTS)
uri, addresses = UrlSafety.validate(raw_url)
response = request(uri, addresses.first, max_bytes)
if response.is_a?(Net::HTTPRedirection)
raise FetchFailed, 'redirect が多すぎます.' if redirects.zero?
location = response['location']
raise FetchFailed, 'redirect 先が不正です.' if location.blank?
return fetch(URI.join(uri, location).to_s,
max_bytes:,
redirects: redirects - 1)
end
unless response.is_a?(Net::HTTPSuccess)
raise FetchFailed, "外部サーバーが HTTP #{ response.code } を返しました."
end
Response.new(response.body, response['content-type'].to_s, uri.to_s)
rescue Net::OpenTimeout, Net::ReadTimeout, Timeout::Error => e
raise FetchTimeout, e.message
rescue SocketError, SystemCallError, OpenSSL::SSL::SSLError, EOFError => e
raise FetchFailed, e.message
end
def self.request(uri, ip_address, max_bytes)
http = Net::HTTP.new(uri.host, uri.port)
http.ipaddr = ip_address
http.use_ssl = uri.scheme == 'https'
http.open_timeout = 5
http.read_timeout = 8
http.write_timeout = 5
request = Net::HTTP::Get.new(uri)
request['User-Agent'] = 'BTRC-Hub thumbnail preview'
request['Accept'] = 'text/html,image/*;q=0.9,*/*;q=0.1'
http.request(request) do |response|
length = response['content-length'].to_i
raise ResponseTooLarge, '外部データが大きすぎます.' if length > max_bytes
body = +''
response.read_body do |chunk|
body << chunk
raise ResponseTooLarge, '外部データが大きすぎます.' if body.bytesize > max_bytes
end
response.instance_variable_set(:@body, body)
response.instance_variable_set(:@read, true)
return response
end
end
private_class_method :request
end
end