# frozen_string_literal: truerequire"forwardable"require"http/form_data"require"http/options"require"http/feature"require"http/headers"require"http/connection"require"http/redirector"require"http/uri"moduleHTTP# Clients make requests and receive responsesclassClientextendForwardableincludeChainableHTTP_OR_HTTPS_RE=%r{^https?://}i.freezedefinitialize(default_options={})@default_options=HTTP::Options.new(default_options)@connection=nil@state=:cleanend# Make an HTTP requestdefrequest(verb,uri,opts={})opts=@default_options.merge(opts)req=build_request(verb,uri,opts)res=perform(req,opts)returnresunlessopts.followRedirector.new(opts.follow).perform(req,res)do|request|perform(wrap_request(request,opts),opts)endend# Prepare an HTTP requestdefbuild_request(verb,uri,opts={})opts=@default_options.merge(opts)uri=make_request_uri(uri,opts)headers=make_request_headers(opts)body=make_request_body(opts,headers)req=HTTP::Request.new(:verb=>verb,:uri=>uri,:uri_normalizer=>opts.feature(:normalize_uri)&.normalizer,:proxy=>opts.proxy,:headers=>headers,:body=>body)wrap_request(req,opts)end# @!method persistent?# @see Options#persistent?# @return [Boolean] whenever client is persistentdef_delegator:default_options,:persistent?# Perform a single (no follow) HTTP requestdefperform(req,options)verify_connection!(req.uri)@state=:dirtybegin@connection||=HTTP::Connection.new(req,options)unless@connection.failed_proxy_connect?@connection.send_request(req)@connection.read_headers!endrescueError=>eoptions.features.each_valuedo|feature|feature.on_error(req,e)endraiseendres=build_response(req,options)res=options.features.inject(res)do|response,(_name,feature)|feature.wrap_response(response)end@connection.finish_responseifreq.verb==:head@state=:cleanresrescuecloseraiseenddefclose@connection&.close@connection=nil@state=:cleanendprivatedefwrap_request(req,opts)opts.features.inject(req)do|request,(_name,feature)|feature.wrap_request(request)endenddefbuild_response(req,options)Response.new(:status=>@connection.status_code,:version=>@connection.http_version,:headers=>@connection.headers,:proxy_headers=>@connection.proxy_response_headers,:connection=>@connection,:encoding=>options.encoding,:request=>req)end# Verify our request isn't going to be made against another URIdefverify_connection!(uri)ifdefault_options.persistent?&&uri.origin!=default_options.persistentraiseStateError,"Persistence is enabled for #{default_options.persistent}, but we got #{uri.origin}"end# We re-create the connection object because we want to let prior requests# lazily load the body as long as possible, and this mimics prior functionality.returncloseif@connection&&(!@connection.keep_alive?||@connection.expired?)# If we get into a bad state (eg, Timeout.timeout ensure being killed)# close the connection to prevent potential for mixed responses.returncloseif@state==:dirtyend# Merges query params if needed## @param [#to_s] uri# @return [URI]defmake_request_uri(uri,opts)uri=uri.to_suri="#{default_options.persistent}#{uri}"ifdefault_options.persistent?&&uri!~HTTP_OR_HTTPS_REuri=HTTP::URI.parseuriuri.query_values=uri.query_values(Array).to_a.concat(opts.params.to_a)ifopts.params&&!opts.params.empty?# Some proxies (seen on WEBRick) fail if URL has# empty path (e.g. `http://example.com`) while it's RFC-complaint:# http://tools.ietf.org/html/rfc1738#section-3.1uri.path="/"ifuri.path.empty?uriend# Creates request headers with cookies (if any) merged indefmake_request_headers(opts)headers=opts.headers# Tell the server to keep the conn openheaders[Headers::CONNECTION]=default_options.persistent??Connection::KEEP_ALIVE:Connection::CLOSEcookies=opts.cookies.valuesunlesscookies.empty?cookies=opts.headers.get(Headers::COOKIE).concat(cookies).join("; ")headers[Headers::COOKIE]=cookiesendheadersend# Create the request body object to senddefmake_request_body(opts,headers)casewhenopts.bodyopts.bodywhenopts.formform=make_form_data(opts.form)headers[Headers::CONTENT_TYPE]||=form.content_typeformwhenopts.jsonbody=MimeType[:json].encodeopts.jsonheaders[Headers::CONTENT_TYPE]||="application/json; charset=#{body.encoding.name.downcase}"bodyendenddefmake_form_data(form)returnformifform.is_a?HTTP::FormData::Multipartreturnformifform.is_a?HTTP::FormData::UrlencodedHTTP::FormData.create(form)endendend