Class: Iodine::Http::Request

Inherits:
Hash
  • Object
show all
Defined in:
lib/iodine/http/request.rb

Overview

This class is the part of the Iodine server. The request object is a Hash and the Request provides simple shortcuts and access to the request’s Hash data.

An Http Request

Defined Under Namespace

Classes: Cookies

Constant Summary collapse

HTTP_GET =

method recognition

'GET'.freeze
HTTP_HEAD =
'HEAD'.freeze
HTTP_POST =
'POST'.freeze
HTTP_PUT =
'PUT'.freeze
HTTP_DELETE =
'DELETE'.freeze
HTTP_TRACE =
'TRACE'.freeze
HTTP_OPTIONS =
'OPTIONS'.freeze
HTTP_CONNECT =
'CONNECT'.freeze
HTTP_PATCH =
'PATCH'.freeze
HTTP_CTYPE =
'content-type'.freeze
HTTP_JSON =
/application\/json/
HTTP_XML =
/text\/xml/

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(io = nil) ⇒ Request

Returns a new instance of Request.



45
46
47
48
49
50
51
# File 'lib/iodine/http/request.rb', line 45

def initialize io = nil
	super()
	self[:io] = io if io
	self[:cookies] = Cookies.new
	self[:params] = {}
	@parsed = false
end

Class Method Details

.add_param_to_hash(name, value, target) ⇒ Object

Adds paramaters to a Hash object, according to the Iodine’s server conventions.



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
# File 'lib/iodine/http/request.rb', line 265

def self.add_param_to_hash name, value, target
	begin
		c = target
		val = rubyfy! value
		a = name.chomp('[]'.freeze).split('['.freeze)

		a[0...-1].inject(target) do |h, n|
			n.chomp!(']'.freeze);
			n.strip!;
			raise "malformed parameter name for #{name}" if n.empty?
			n = (n.to_i.to_s == n) ?  n.to_i : n.to_sym            
			c = (h[n] ||= {})
		end
		n = a.last
		n.chomp!(']'); n.strip!;
		n = n.empty? ? nil : ( (n.to_i.to_s == n) ?  n.to_i : n.to_sym )
		if n
			if c[n]
				c[n].is_a?(Array) ? (c[n] << val) : (c[n] = [c[n], val])
			else
				c[n] = val
			end
		else
			if c[n]
				c[n].is_a?(Array) ? (c[n] << val) : (c[n] = [c[n], val])
			else
				c[n] = [val]
			end
		end
		val
	rescue => e
		Iodine.error e
		Iodine.error "(Silent): parameters parse error for #{name} ... maybe conflicts with a different set?"
		target[name] = val
	end
end

.encode_url(str) ⇒ Object

encodes URL data.



260
261
262
# File 'lib/iodine/http/request.rb', line 260

def self.encode_url str
	(str.to_s.gsub(/[^a-z0-9\*\.\_\-]/i) {|m| '%%%02x'.freeze % m.ord }).force_encoding(::Encoding::ASCII_8BIT)
end

.extract_header(data, target_hash) ⇒ Object

extracts parameters from header data



315
316
317
318
319
320
321
# File 'lib/iodine/http/request.rb', line 315

def self.extract_header data, target_hash
	data.each do |set|
		list = set.split('='.freeze, 2)
		list.each {|s| form_decode!(s) if s}
		add_param_to_hash list.shift, list.shift, target_hash
	end
end

.extract_params(data, target_hash) ⇒ Object

extracts parameters from the query



303
304
305
306
307
308
309
# File 'lib/iodine/http/request.rb', line 303

def self.extract_params data, target_hash
	data.each do |set|
		list = set.split('='.freeze, 2)
		list.each {|s| uri_decode!(s) if s}
		add_param_to_hash list.shift, list.shift, target_hash
	end
end

.form_decode!(s) ⇒ Object

decode percent-encoded data (excluding the ‘+’ sign for encoding).



323
324
325
# File 'lib/iodine/http/request.rb', line 323

def self.form_decode! s
	s.gsub!(/\%[0-9a-f]{2}/i) {|m| m[1..2].to_i(16).chr}; s.gsub!(/&#[0-9]{4};/i) {|m| [m[2..5].to_i].pack 'U'.freeze }; s
end

.make_utf8!(string, encoding = ::Encoding::UTF_8) ⇒ Object

re-encodes a string into UTF-8



246
247
248
249
250
# File 'lib/iodine/http/request.rb', line 246

def self.make_utf8!(string, encoding= ::Encoding::UTF_8)
	return false unless string
	string.force_encoding(::Encoding::ASCII_8BIT).encode!(encoding, ::Encoding::ASCII_8BIT, invalid: :replace, undef: :replace, replace: ''.freeze) unless string.force_encoding(encoding).valid_encoding?
	string
end

.parse(request) ⇒ Object

parses an HTTP request (quary, body data etc’)



195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# File 'lib/iodine/http/request.rb', line 195

def self.parse request
	# if m = request[:query].match /(([a-z0-9A-Z]+):\/\/)?(([^\/\:]+))?(:([0-9]+))?([^\?\#]*)(\?([^\#]*))?/
		# request[:requested_protocol] = m[1] || request['x-forwarded-proto'] || ( request[:io].ssl? ? 'https' : 'http')
		# request[:host_name] = m[4] || (request['host'] ? request['host'].match(/^[^:]*/).to_s : nil)
		# request[:port] = m[6] || (request['host'] ? request['host'].match(/:([0-9]*)/).to_a[1] : nil)
		# request[:original_path] = HTTP.decode(m[7], :uri) || '/'
		# request['host'] ||= "#{request[:host_name]}:#{request[:port]}"

		# # parse query for params - m[9] is the data part of the query
		# if m[9]
		# 	extract_params m[9].split(/[&;]/), request[:params]
		# end
	# end
	return request if request[:client_ip]
	request[:client_ip] = request['x-forwarded-for'.freeze].to_s.split(/,[\s]?/)[0] || (request[:io].io.to_io.remote_address.ip_address) rescue 'unknown IP'.freeze
	request[:version] ||= '1'

	request[:scheme] ||= request['x-forwarded-proto'.freeze] ? request['x-forwarded-proto'.freeze].downcase : ( request[:io].ssl? ? 'https'.freeze : 'http'.freeze)
	tmp = (request['host'.freeze] || request[:authority] || ''.freeze).split(':')
	request[:host_name] = tmp[0]
	request[:port] = tmp[1] || nil

	tmp = (request[:query] ||= request[:path] ).split('?', 2)
	request[:path] = tmp[0].chomp('/')
	request[:original_path] = tmp[0].freeze
	request[:quary_params] = tmp[1]
	extract_params tmp[1].split(/[&;]/.freeze), (request[:params] ||= {}) if tmp[1]

	if request['cookie'.freeze]
		if request['cookie'.freeze].is_a?(Array)
			tmp = []
			request['cookie'.freeze].each {|s| s.split(/[;,][\s]?/.freeze).each { |c| tmp << c } }
			request['cookie'.freeze] = tmp
			extract_header tmp, request.cookies
		else
			extract_header request['cookie'.freeze].split(/[;,][\s]?/.freeze), request.cookies
		end
	elsif request['set-cookie'.freeze]
		request['set-cookie'.freeze] = [ request['set-cookie'.freeze] ] unless request['set-cookie'.freeze].is_a?(Array)
		tmp = []
		request['set-cookie'.freeze].each {|s| tmp << s.split(/[;][\s]?/.freeze)[0] }
		request['set-cookie'.freeze] = tmp
		extract_header tmp, request.cookies
	end

	read_body request if request[:body]

	request
end

.read_body(request) ⇒ Object

read the body’s data and parse any incoming data.



341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
# File 'lib/iodine/http/request.rb', line 341

def self.read_body request
	# save body for Rack, if applicable
	request[:rack_input] = StringIO.new(request[:body].dup.force_encoding(::Encoding::ASCII_8BIT)) if ::Iodine::Http.on_http == ::Iodine::Http::Rack
	# parse content
	case request['content-type'.freeze].to_s
	when /x-www-form-urlencoded/
		extract_params request.delete(:body).split(/[&;]/), request[:params] #, :form # :uri
	when /multipart\/form-data/
		read_multipart request, request, request.delete(:body)
	when /text\/xml/
		# to-do support xml?
		make_utf8! request[:body]
		nil
	when /application\/json/
		JSON.parse(make_utf8! request[:body]).each {|k, v| add_param_to_hash k, v, request[:params]} rescue true
	end
end

.read_multipart(request, headers, part, name_prefix = '') ⇒ Object

parse a mime/multipart body or part.



360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
# File 'lib/iodine/http/request.rb', line 360

def self.read_multipart request, headers, part, name_prefix = ''
	if headers['content-type'].to_s =~ /multipart/i
		tmp = {}
		extract_header headers['content-type'].split(/[;,][\s]?/), tmp
		boundry = tmp[:boundary]
		if tmp[:name]
			if name_prefix.empty?
				name_prefix << tmp[:name]
			else
				name_prefix << "[#{tmp[:name]}]"
			end
		end
		part.split(/([\r]?\n)?--#{boundry}(--)?[\r]?\n/).each do |p|
			unless p.strip.empty? || p=='--'.freeze
				# read headers
				h = {}
				m = p.slice! /\A[^\r\n]*[\r]?\n/
				while m
					break if m =~ /\A[\r]?\n/
					m = m.match(/^([^:]+):[\s]?([^\r\n]+)/)
					h[m[1].downcase] = m[2] if m
					m = p.slice! /\A[^\r\n]*[\r]?\n/
				end
				# send headers and body to be read
				read_multipart request, h, p, name_prefix
			end
		end
		return
	end

	# require a part body to exist (data exists) for parsing
	return true if part.to_s.empty?

	# convert part to `charset` if charset is defined?

	if !headers['content-disposition'.freeze]
		Iodine.error "Wrong multipart format with headers: #{headers} and body: #{part}"
		return
	end

	cd = {}

	extract_header headers['content-disposition'.freeze].split(/[;,][\s]?/), cd

	if name_prefix.empty?
		name = cd[:name][1..-2]
	else
		name = "#{name_prefix}[cd[:name][1..-2]}]"
	end
	if headers['content-type'.freeze]
		add_param_to_hash "#{name}[data]", part, request[:params]
		add_param_to_hash "#{name}[type]", make_utf8!(headers['content-type'.freeze]), request[:params]
		cd.each {|k,v|  add_param_to_hash "#{name}[#{k.to_s}]", make_utf8!(v[1..-2].to_s), request[:params] unless k == :name || !v}
	else
		add_param_to_hash name, uri_decode!(part), request[:params]
	end
	true
end

.rubyfy!(string) ⇒ Object

Changes String to a Ruby Object, if it’s a special string…



327
328
329
330
331
332
333
334
335
336
337
338
# File 'lib/iodine/http/request.rb', line 327

def self.rubyfy!(string)
	return string unless string.is_a?(String)
	try_utf8! string
	if string == 'true'.freeze
		string = true
	elsif string == 'false'.freeze
		string = false
	elsif string.to_i.to_s == string
		string = string.to_i
	end
	string
end

.try_utf8!(string, encoding = ::Encoding::UTF_8) ⇒ Object

re-encodes a string into UTF-8



253
254
255
256
257
# File 'lib/iodine/http/request.rb', line 253

def self.try_utf8!(string, encoding= ::Encoding::UTF_8)
	return false unless string
	string.force_encoding(::Encoding::ASCII_8BIT) unless string.force_encoding(encoding).valid_encoding?
	string
end

.uri_decode!(s) ⇒ Object

decode form / uri data (including the ‘+’ sign as a space (%20) replacement).



311
312
313
# File 'lib/iodine/http/request.rb', line 311

def self.uri_decode! s
	s.gsub!('+'.freeze, '%20'.freeze); s.gsub!(/\%[0-9a-f]{2}/i) {|m| m[1..2].to_i(16).chr}; s.gsub!(/&#[0-9]{4};/i) {|m| [m[2..5].to_i].pack 'U'.freeze }; s
end

Instance Method Details

#base_url(switch_scheme = nil) ⇒ Object

the base url ([http/https]://host)



100
101
102
# File 'lib/iodine/http/request.rb', line 100

def base_url switch_scheme = nil
	"#{switch_scheme || self[:scheme]}://#{self[:host_name]}#{self[:port]? ":#{self[:port]}" : ''}"
end

#connect?Boolean

returns true of the method == CONNECT

Returns:

  • (Boolean)


170
171
172
# File 'lib/iodine/http/request.rb', line 170

def connect?
	self[:method] == HTTP_CONNECT
end

#cookiesObject

the cookies sent by the client.



72
73
74
# File 'lib/iodine/http/request.rb', line 72

def cookies
	self[:cookies]
end

#delete?Boolean

returns true of the method == DELETE

Returns:

  • (Boolean)


155
156
157
# File 'lib/iodine/http/request.rb', line 155

def delete?
	self[:method] == HTTP_DELETE
end

#get?Boolean

returns true of the method == GET

Returns:

  • (Boolean)


134
135
136
# File 'lib/iodine/http/request.rb', line 134

def get?
	self[:method] == HTTP_GET
end

#head?Boolean

returns true of the method == HEAD

Returns:

  • (Boolean)


140
141
142
# File 'lib/iodine/http/request.rb', line 140

def head?
	self[:method] == HTTP_HEAD
end

#headersObject

the request’s headers



56
57
58
# File 'lib/iodine/http/request.rb', line 56

def headers
	self.select {|k,v| k.is_a? String }
end

#ioIodine::Http, ...

Returns the Protocol used for the request.

Returns:

  • (Iodine::Http, Iodine::Http2, Iodine::Websockets)

    the Protocol used for the request.



121
122
123
# File 'lib/iodine/http/request.rb', line 121

def io
	self[:io]
end

#json?Boolean

returns true if the request is of type JSON.

Returns:

  • (Boolean)


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

def json?
	self[HTTP_CTYPE] =~ HTTP_JSON
end

#options?Boolean

returns true of the method == OPTIONS

Returns:

  • (Boolean)


165
166
167
# File 'lib/iodine/http/request.rb', line 165

def options?
	self[:method] == HTTP_OPTIONS
end

#original_pathObject

the original (frozen) path (resource requested).



82
83
84
# File 'lib/iodine/http/request.rb', line 82

def original_path
	self[:original_path]
end

#paramsObject

the parameters sent by the client.



68
69
70
# File 'lib/iodine/http/request.rb', line 68

def params
	self[:params]
end

#patch?Boolean

returns true of the method == PATCH

Returns:

  • (Boolean)


175
176
177
# File 'lib/iodine/http/request.rb', line 175

def patch?
	self[:method] == HTTP_PATCH
end

#pathObject

the requested path (rewritable).



87
88
89
# File 'lib/iodine/http/request.rb', line 87

def path
	self[:path]
end

#path=(new_path) ⇒ Object



90
91
92
# File 'lib/iodine/http/request.rb', line 90

def path=(new_path)
	self[:path] = new_path
end

#post?Boolean

returns true of the method == POST

Returns:

  • (Boolean)


145
146
147
# File 'lib/iodine/http/request.rb', line 145

def post?
	self[:method] == HTTP_POST
end

#put?Boolean

returns true of the method == PUT

Returns:

  • (Boolean)


150
151
152
# File 'lib/iodine/http/request.rb', line 150

def put?
	self[:method] == HTTP_PUT
end

#queryObject

the query string



77
78
79
# File 'lib/iodine/http/request.rb', line 77

def query
	self[:query]
end

#request_methodObject

the request’s method (GET, POST… etc’).



60
61
62
# File 'lib/iodine/http/request.rb', line 60

def request_method
	self[:method]
end

#request_method=(value) ⇒ Object

set request’s method (GET, POST… etc’).



64
65
66
# File 'lib/iodine/http/request.rb', line 64

def request_method= value
	self[:method] = value
end

#request_url(switch_scheme = nil) ⇒ Object

the request’s url, without any GET parameters ([http/https]://host/path)



105
106
107
# File 'lib/iodine/http/request.rb', line 105

def request_url switch_scheme = nil
	"#{base_url switch_scheme}#{self[:original_path]}"
end

#schemeObject

the protocol’s scheme (http/https/ws/wss) managing this request



110
111
112
# File 'lib/iodine/http/request.rb', line 110

def scheme
	self[:scheme]
end

#sessionHash like storage

Returns the session storage object IF a session was already initialized (use the response to initialize a session).

Returns:

  • (Hash like storage)

    Returns the session storage object IF a session was already initialized (use the response to initialize a session).



126
127
128
# File 'lib/iodine/http/request.rb', line 126

def session
	self[:session]
end

#ssl?true, false Also known as: secure?

Returns true if the requested was an SSL protocol (true also if the connection is clear-text behind an SSL Proxy, such as with some PaaS providers).

Returns:

  • (true, false)

    returns true if the requested was an SSL protocol (true also if the connection is clear-text behind an SSL Proxy, such as with some PaaS providers).



115
116
117
# File 'lib/iodine/http/request.rb', line 115

def ssl?
	self[:io].ssl? || self[:scheme] == 'https'.freeze || self[:scheme] == 'wss'.freeze
end

#trace?Boolean

returns true of the method == TRACE

Returns:

  • (Boolean)


160
161
162
# File 'lib/iodine/http/request.rb', line 160

def trace?
	self[:method] == HTTP_TRACE
end

#versionObject

The HTTP version for this request



95
96
97
# File 'lib/iodine/http/request.rb', line 95

def version
	self[:version]
end

#websocket?Boolean Also known as: upgrade?

returns true if this is a websocket upgrade request

Returns:

  • (Boolean)


189
190
191
# File 'lib/iodine/http/request.rb', line 189

def websocket?
	@is_websocket ||= (self['upgrade'.freeze] && self['upgrade'.freeze].to_s =~ /websocket/i.freeze &&  self['connection'.freeze].to_s =~ /upg/i.freeze && true)
end

#xml?Boolean

returns true if the request is of type XML.

Returns:

  • (Boolean)


185
186
187
# File 'lib/iodine/http/request.rb', line 185

def xml?
	self[HTTP_CTYPE].match HTTP_XML
end