Class: Tina4::Request

Inherits:
Object
  • Object
show all
Defined in:
lib/tina4/request.rb

Defined Under Namespace

Classes: PayloadTooLarge

Constant Summary collapse

TINA4_MAX_UPLOAD_SIZE =

Maximum upload size in bytes (default 10 MB). Override via TINA4_MAX_UPLOAD_SIZE env var.

Integer(ENV.fetch("TINA4_MAX_UPLOAD_SIZE", 10_485_760))

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(env, path_params = {}) ⇒ Request

Returns a new instance of Request.



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/tina4/request.rb', line 125

def initialize(env, path_params = {})
  @env = env
  @method = env["REQUEST_METHOD"]
  @path = env["PATH_INFO"] || "/"
  @query_string = env["QUERY_STRING"] || ""
  @content_type = env["CONTENT_TYPE"] || ""
  @path_params = path_params

  # Check upload size limit
  content_length = (env["CONTENT_LENGTH"] || 0).to_i
  if content_length > TINA4_MAX_UPLOAD_SIZE
    raise PayloadTooLarge,
      "Request body (#{content_length} bytes) exceeds TINA4_MAX_UPLOAD_SIZE (#{TINA4_MAX_UPLOAD_SIZE} bytes)"
  end

  # Client IP with X-Forwarded-For support
  @ip = extract_client_ip

  # Lazy-initialized fields (nil = not yet computed)
  @headers = nil
  @cookies = nil
  @session = nil
  @body_raw = nil
  @params = nil
  @files = nil
  @json_body = nil
  @query_hash = nil
  @body_parsed = nil
end

Instance Attribute Details

#content_typeObject (readonly)

Returns the value of attribute content_type.



116
117
118
# File 'lib/tina4/request.rb', line 116

def content_type
  @content_type
end

#envObject (readonly)

Returns the value of attribute env.



116
117
118
# File 'lib/tina4/request.rb', line 116

def env
  @env
end

#ipObject (readonly)

Returns the value of attribute ip.



116
117
118
# File 'lib/tina4/request.rb', line 116

def ip
  @ip
end

#methodObject (readonly)

Returns the value of attribute method.



116
117
118
# File 'lib/tina4/request.rb', line 116

def method
  @method
end

#pathObject (readonly)

Returns the value of attribute path.



116
117
118
# File 'lib/tina4/request.rb', line 116

def path
  @path
end

#path_paramsObject (readonly)

Returns the value of attribute path_params.



116
117
118
# File 'lib/tina4/request.rb', line 116

def path_params
  @path_params
end

#query_stringObject (readonly)

Returns the value of attribute query_string.



116
117
118
# File 'lib/tina4/request.rb', line 116

def query_string
  @query_string
end

#userObject

Returns the value of attribute user.



118
119
120
# File 'lib/tina4/request.rb', line 118

def user
  @user
end

Instance Method Details

#[](key) ⇒ Object



223
224
225
# File 'lib/tina4/request.rb', line 223

def [](key)
  params[key.to_s] || params[key.to_sym] || @path_params[key.to_sym]
end

#bearer_tokenObject



242
243
244
245
# File 'lib/tina4/request.rb', line 242

def bearer_token
  auth = header("authorization") || ""
  auth.sub(/\ABearer\s+/i, "") if auth =~ /\ABearer\s+/i
end

#bodyObject Also known as: body_parsed

Parsed body (JSON -> Hash, form-urlencoded -> Hash, multipart -> fields Hash, else the current fallback). This matches Python’s ‘request.body`, PHP’s, and Node’s: ‘body` is the PARSED payload, not the raw bytes. For the raw string use `body_raw`.



188
189
190
# File 'lib/tina4/request.rb', line 188

def body
  @body_parsed ||= parse_body
end

#body_rawObject

Raw body string — the bytes exactly as the client sent them. (This is what ‘body` used to return before the cross-framework parity flip; SOAP/GraphQL and any consumer that needs the raw text reads this.)



196
197
198
# File 'lib/tina4/request.rb', line 196

def body_raw
  @body_raw ||= read_body
end

#cookiesObject



176
177
178
# File 'lib/tina4/request.rb', line 176

def cookies
  @cookies ||= parse_cookies
end

#filesObject



208
209
210
# File 'lib/tina4/request.rb', line 208

def files
  @files ||= extract_files
end

#header(name) ⇒ Object



227
228
229
230
231
232
# File 'lib/tina4/request.rb', line 227

def header(name)
  # Headers are stored in a CaseInsensitiveHash keyed by lowercase-
  # dashed names ("content-type", "x-api-key"). The hash normalises
  # the lookup case automatically, so pass the dashed form through.
  headers[name.to_s.tr("_", "-")]
end

#headersObject

Lazy accessors



172
173
174
# File 'lib/tina4/request.rb', line 172

def headers
  @headers ||= extract_headers
end

#json_bodyObject



234
235
236
237
238
239
240
# File 'lib/tina4/request.rb', line 234

def json_body
  @json_body ||= begin
    JSON.parse(body_raw)
  rescue JSON::ParserError, TypeError
    {}
  end
end

#param(key, default = nil) ⇒ Object

Look up a param by symbol or string key (indifferent access shortcut).



219
220
221
# File 'lib/tina4/request.rb', line 219

def param(key, default = nil)
  params[key.to_s] || params[key.to_sym] || default
end

#paramsObject

Merged params: query + body + path_params (path_params highest priority) Supports both string and symbol key access (indifferent access).



214
215
216
# File 'lib/tina4/request.rb', line 214

def params
  @params ||= build_params
end

#queryObject

Parsed query string as hash



204
205
206
# File 'lib/tina4/request.rb', line 204

def query
  @query_hash ||= parse_query_to_hash(@query_string)
end

#sessionObject



180
181
182
# File 'lib/tina4/request.rb', line 180

def session
  @session ||= Tina4::Session.new(@env)
end

#urlObject

Full absolute URL — scheme://host/path. Honours X-Forwarded-Proto / X-Forwarded-Host so apps behind a proxy still see the URL the client used. Matches Python/PHP/Node parity.



158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/tina4/request.rb', line 158

def url
  scheme = env["HTTP_X_FORWARDED_PROTO"] || env["rack.url_scheme"] || "http"
  host = env["HTTP_X_FORWARDED_HOST"] || env["HTTP_HOST"] || env["SERVER_NAME"] || "localhost"
  port = env["SERVER_PORT"]
  url_str = "#{scheme}://#{host}"
  # Only append :port when the host doesn't already include one
  # (HTTP_HOST often does) and it's not the default for the scheme.
  url_str += ":#{port}" if port && !host.include?(":") && port.to_s != "80" && port.to_s != "443"
  url_str += @path
  url_str += "?#{@query_string}" unless @query_string.empty?
  url_str
end