File: request.rb

package info (click to toggle)
ruby-gitlab 6.1.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 2,824 kB
  • sloc: ruby: 12,742; makefile: 7; sh: 4; javascript: 3
file content (139 lines) | stat: -rw-r--r-- 4,773 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# frozen_string_literal: true

require 'httparty'
require 'json'

module Gitlab
  # @private
  class Request
    include HTTParty
    format :json
    maintain_method_across_redirects true
    headers 'Accept' => 'application/json', 'Content-Type' => 'application/x-www-form-urlencoded'
    parser(proc { |body, _| parse(body) })

    attr_accessor :private_token, :endpoint, :pat_prefix, :body_as_json

    # Converts the response body to an ObjectifiedHash.
    def self.parse(body)
      body = decode(body)

      if body.is_a? Hash
        ObjectifiedHash.new body
      elsif body.is_a? Array
        PaginatedResponse.new(body.collect! { |e| ObjectifiedHash.new(e) })
      elsif body
        true
      elsif !body
        false
      else
        raise Error::Parsing, "Couldn't parse a response body"
      end
    end

    # Decodes a JSON response into Ruby object.
    def self.decode(response)
      response ? JSON.load(response) : {}
    rescue JSON::ParserError
      raise Error::Parsing, 'The response is not a valid JSON'
    end

    %w[get post put patch delete].each do |method|
      define_method method do |path, options = {}|
        params = options.dup

        httparty_config(params)

        unless params[:unauthenticated]
          params[:headers] ||= {}
          params[:headers].merge!(authorization_header)
        end

        jsonify_body_content(params) if body_as_json

        retries_left = params[:ratelimit_retries] || 3
        begin
          response = self.class.send(method, endpoint + path, params)
          validate response
        rescue Gitlab::Error::TooManyRequests => e
          retries_left -= 1
          raise e if retries_left.zero?

          wait_time = response.headers['Retry-After'] || 2
          sleep(wait_time.to_i)
          retry
        end
      end
    end

    # Checks the response code for common errors.
    # Returns parsed response for successful requests.
    def validate(response)
      error_klass = Error.klass(response)
      raise error_klass, response if error_klass

      parsed = response.parsed_response
      parsed.client = self if parsed.respond_to?(:client=)
      parsed.parse_headers!(response.headers) if parsed.respond_to?(:parse_headers!)
      parsed
    end

    # Sets a base_uri and default_params for requests.
    # @raise [Error::MissingCredentials] if endpoint not set.
    def request_defaults(sudo = nil)
      raise Error::MissingCredentials, 'Please set an endpoint to API' unless endpoint

      self.class.default_params sudo: sudo
      self.class.default_params.delete(:sudo) if sudo.nil?
    end

    private

    # Returns an Authorization header hash
    #
    # @raise [Error::MissingCredentials] if private_token and auth_token are not set.
    def authorization_header
      raise Error::MissingCredentials, 'Please provide a private_token or auth_token for user' unless private_token

      # The Personal Access Token prefix can be at most 20 characters, and the
      # generated part is of length 20 characters. Personal Access Tokens, thus
      # can have a maximum size of 40 characters. GitLab uses
      # `Doorkeeper::OAuth::Helpers::UniqueToken.generate` for generating
      # OAuth2 tokens, and specified `hex` as token generator method. Thus, the
      # OAuth2 tokens are of length more than 64. If the token length is below
      # that, it is probably a Personal Access Token or CI_JOB_TOKEN.
      if private_token.size >= 64
        { 'Authorization' => "Bearer #{private_token}" }
      elsif private_token.start_with?(pat_prefix.to_s)
        { 'PRIVATE-TOKEN' => private_token }
      else
        { 'JOB-TOKEN' => private_token }
      end
    end

    # Set HTTParty configuration
    # @see https://github.com/jnunemaker/httparty
    def httparty_config(options)
      options.merge!(httparty) if httparty
    end

    # Handle 'body_as_json' configuration option
    # Modifies passed params in place.
    def jsonify_body_content(params)
      # Only modify the content type if there is a body to process AND multipath
      # was not explicitly requested. There are no uses of multipart in this code
      # today, but file upload methods require it and someone might be manually
      # crafting a post call with it:
      return unless params[:body] && params[:multipart] != true

      # If the caller explicitly requested a Content-Type during the call, assume
      # they know best and have formatted the body as required:
      return if params[:headers]&.key?('Content-Type')

      # If we make it here, then we assume it is safe to JSON encode the body:
      params[:headers] ||= {}
      params[:headers]['Content-Type'] = 'application/json'
      params[:body] = params[:body].to_json
    end
  end
end