Class: Merb::Request

Inherits:
Object show all
Includes:
ControllerExceptions
Defined in:
lib/merb-core/dispatch/request.rb,
lib/merb-core/dispatch/dispatcher.rb

Constant Summary collapse

METHODS =
%w{get post put delete head options}
NAME_REGEX =
/Content-Disposition:.* name="?([^\";]*)"?/ni.freeze
CONTENT_TYPE_REGEX =
/Content-Type: (.*)\r\n/ni.freeze
FILENAME_REGEX =
/Content-Disposition:.* filename="?([^\";]*)"?/ni.freeze
CRLF =
"\r\n".freeze
EOL =
CRLF
@@mutex =
Mutex.new

Constants included from ControllerExceptions

ControllerExceptions::STATUS_CODES

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(rack_env) ⇒ Request

Initialize the request object.

Parameters

http_request<~params:~[], ~body:IO>

An object like an HTTP Request.



32
33
34
35
36
# File 'lib/merb-core/dispatch/request.rb', line 32

def initialize(rack_env)
  @env  = rack_env
  @body = rack_env['rack.input']
  @route_params = {}
end

Instance Attribute Details

#envObject

def env def exceptions def route_params



7
8
9
# File 'lib/merb-core/dispatch/request.rb', line 7

def env
  @env
end

#exceptionsObject

def env def exceptions def route_params



7
8
9
# File 'lib/merb-core/dispatch/request.rb', line 7

def exceptions
  @exceptions
end

#routeObject

def env def exceptions def route_params



7
8
9
# File 'lib/merb-core/dispatch/request.rb', line 7

def route
  @route
end

#route_paramsObject (readonly)

Returns the value of attribute route_params.



8
9
10
# File 'lib/merb-core/dispatch/request.rb', line 8

def route_params
  @route_params
end

Class Method Details

.escape(s) ⇒ Object

Parameters

s<String>

String to URL escape.

returns

String

The escaped string.



548
549
550
551
552
# File 'lib/merb-core/dispatch/request.rb', line 548

def escape(s)
  s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
    '%'+$1.unpack('H2'*$1.size).join('%').upcase
  }.tr(' ', '+')
end

.normalize_params(parms, name, val = nil) ⇒ Object

Converts a query string snippet to a hash and adds it to existing parameters.

Parameters

parms<Hash>

Parameters to add the normalized parameters to.

name<String>

The key of the parameter to normalize.

val<String>

The value of the parameter.

Returns

Hash

Normalized parameters



693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
# File 'lib/merb-core/dispatch/request.rb', line 693

def normalize_params(parms, name, val=nil)
  name =~ %r([\[\]]*([^\[\]]+)\]*)
  key = $1 || ''
  after = $' || ''
  
  if after == ""
    parms[key] = val
  elsif after == "[]"
    (parms[key] ||= []) << val
  elsif after =~ %r(^\[\]\[([^\[\]]+)\]$)
    child_key = $1
    parms[key] ||= []
    if parms[key].last.is_a?(Hash) && !parms[key].last.key?(child_key)
      parms[key].last.update(child_key => val)
    else
      parms[key] << { child_key => val }
    end
  else
    parms[key] ||= {}
    parms[key] = normalize_params(parms[key], after, val)
  end
  parms
end

.params_to_query_string(value, prefix = nil) ⇒ Object

Parameters

value<Array, Hash, Dictionary ~to_s>

The value for the query string.

prefix<~to_s>

The prefix to add to the query string keys.

Returns

String

The query string.

Alternatives

If the value is a string, the prefix will be used as the key.

Examples

params_to_query_string(10, "page")
  # => "page=10"
params_to_query_string({ :page => 10, :word => "ruby" })
  # => "page=10&word=ruby"
params_to_query_string({ :page => 10, :word => "ruby" }, "search")
  # => "search[page]=10&search[word]=ruby"
params_to_query_string([ "ice-cream", "cake" ], "shopping_list")
  # => "shopping_list[]=ice-cream&shopping_list[]=cake"


528
529
530
531
532
533
534
535
536
537
538
539
540
541
# File 'lib/merb-core/dispatch/request.rb', line 528

def params_to_query_string(value, prefix = nil)
  case value
  when Array
    value.map { |v|
      params_to_query_string(v, "#{prefix}[]")
    } * "&"
  when Hash, Dictionary
    value.map { |k, v|
      params_to_query_string(v, prefix ? "#{prefix}[#{Merb::Request.escape(k)}]" : Merb::Request.escape(k))
    } * "&"
  else
    "#{prefix}=#{Merb::Request.escape(value)}"
  end
end

.parse_multipart(request, boundary, content_length) ⇒ Object

Parameters

request<IO>

The raw request.

boundary<String>

The boundary string.

content_length<Fixnum>

The length of the content.

Raises

ControllerExceptions::MultiPartParseError

Failed to parse request.

Returns

Hash

The parsed request.



606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
# File 'lib/merb-core/dispatch/request.rb', line 606

def parse_multipart(request, boundary, content_length)
  boundary = "--#{boundary}"
  paramhsh = {}
  buf = ""
  input = request
  input.binmode if defined? input.binmode
  boundary_size = boundary.size + EOL.size
  bufsize = 16384
  content_length -= boundary_size
  status = input.read(boundary_size)
  return {} if status == nil || status.empty?
  raise ControllerExceptions::MultiPartParseError, "bad content body:\n'#{status}' should == '#{boundary + EOL}'"  unless status == boundary + EOL
  rx = /(?:#{EOL})?#{Regexp.quote(boundary,'n')}(#{EOL}|--)/
  loop {
    head = nil
    body = ''
    filename = content_type = name = nil
    read_size = 0
    until head && buf =~ rx
      i = buf.index("\r\n\r\n")
      if( i == nil && read_size == 0 && content_length == 0 )
        content_length = -1
        break
      end
      if !head && i
        head = buf.slice!(0, i+2) # First \r\n
        buf.slice!(0, 2)          # Second \r\n
        filename = head[FILENAME_REGEX, 1]
        content_type = head[CONTENT_TYPE_REGEX, 1]
        name = head[NAME_REGEX, 1]
      
        if filename && !filename.empty?
          body = Tempfile.new(:Merb)
          body.binmode if defined? body.binmode
        end
        next
      end
    
      # Save the read body part.
      if head && (boundary_size+4 < buf.size)
        body << buf.slice!(0, buf.size - (boundary_size+4))
      end
    
      read_size = bufsize < content_length ? bufsize : content_length
      if( read_size > 0 )
        c = input.read(read_size)
        raise ControllerExceptions::MultiPartParseError, "bad content body"  if c.nil? || c.empty?
        buf << c
        content_length -= c.size
      end
    end
  
    # Save the rest.
    if i = buf.index(rx)
      body << buf.slice!(0, i)
      buf.slice!(0, boundary_size+2)
    
      content_length = -1  if $1 == "--"
    end
  
    if filename && !filename.empty?   
      body.rewind
      data = { 
        :filename => File.basename(filename),  
        :content_type => content_type,  
        :tempfile => body, 
        :size => File.size(body.path) 
      }
    else
      data = body
    end
    paramhsh = normalize_params(paramhsh,name,data)
    break  if buf.empty? || content_length == -1
  }
  paramhsh
end

.query_parse(query_string, delimiter = '&;', preserve_order = false) ⇒ Object

Parameters

query_string<String>

The query string.

delimiter<String>

The query string divider. Defaults to “&”.

preserve_order<Boolean>

Preserve order of args. Defaults to false.

Returns

Mash

The parsed query string (Dictionary if preserve_order is set).

Examples

query_parse("bar=nik&post[body]=heya")
  # => { :bar => "nik", :post => { :body => "heya" } }


576
577
578
579
580
581
582
583
584
585
586
587
588
# File 'lib/merb-core/dispatch/request.rb', line 576

def query_parse(query_string, delimiter = '&;', preserve_order = false)
  query = preserve_order ? Dictionary.new : {}
  for pair in (query_string || '').split(/[#{delimiter}] */n)
    key, value = unescape(pair).split('=',2)
    next if key.nil?
    if key.include?('[')
      normalize_params(query, key, value)
    else        
      query[key] = value
    end
  end
  preserve_order ? query : query.to_mash
end

.unescape(s) ⇒ Object

Parameter

s<String>

String to URL unescape.

returns

String

The unescaped string.



559
560
561
562
563
# File 'lib/merb-core/dispatch/request.rb', line 559

def unescape(s)
  s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){
    [$1.delete('%')].pack('H*')
  }
end

Instance Method Details

#_process_block_return(retval) ⇒ Object

Processes the return value of a deferred router block and returns the current route params for the current request evaluation




106
107
108
109
110
111
112
113
114
115
116
# File 'lib/merb-core/dispatch/request.rb', line 106

def _process_block_return(retval)
  # If the return value is an array, then it is a redirect
  # so we must set the request as a redirect and extract
  # the redirect params and return it as a hash so that the
  # dispatcher can handle it
  if retval.is_a?(Array)
    redirects!
    return { :url => retval[0], :status => retval[1] } 
  end
  retval
end

#acceptObject

Returns

String

The accepted response types. Defaults to “/”.



414
415
416
# File 'lib/merb-core/dispatch/request.rb', line 414

def accept
  @env['HTTP_ACCEPT'].blank? ? "*/*" : @env['HTTP_ACCEPT']
end

#accept_charsetObject

Returns

String

The accepted character sets.



396
397
398
# File 'lib/merb-core/dispatch/request.rb', line 396

def accept_charset
  @env['HTTP_ACCEPT_CHARSET']
end

#accept_encodingObject

Returns

String

The accepted encodings.



360
361
362
# File 'lib/merb-core/dispatch/request.rb', line 360

def accept_encoding
  @env['HTTP_ACCEPT_ENCODING']
end

#accept_languageObject

Returns

String

The accepted language.



378
379
380
# File 'lib/merb-core/dispatch/request.rb', line 378

def accept_language
  @env['HTTP_ACCEPT_LANGUAGE']
end

#cache_controlObject

Returns

String

HTTP cache control.



372
373
374
# File 'lib/merb-core/dispatch/request.rb', line 372

def cache_control
  @env['HTTP_CACHE_CONTROL']
end

#connectionObject

Returns

String

The HTTP connection.



420
421
422
# File 'lib/merb-core/dispatch/request.rb', line 420

def connection
  @env['HTTP_CONNECTION']
end

#content_lengthObject

Returns

Fixnum

The request content length.



438
439
440
# File 'lib/merb-core/dispatch/request.rb', line 438

def content_length
  @content_length ||= @env[Merb::Const::CONTENT_LENGTH].to_i
end

#content_typeObject

Returns

String

The request content type.



432
433
434
# File 'lib/merb-core/dispatch/request.rb', line 432

def content_type
  @env['CONTENT_TYPE']
end

#controllerObject



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/merb-core/dispatch/request.rb', line 38

def controller
  unless params[:controller]
    raise ControllerExceptions::NotFound, 
      "Route matched, but route did not specify a controller.\n" +
      "Did you forgot to add :controller => \"people\" or :controller " +
      "segment to route definition?\nHere is what's specified:\n" + 
      route.inspect
  end
  path = [params[:namespace], params[:controller]].compact.join("/")
  controller = path.snake_case.to_const_string
  
  begin
    Object.full_const_get(controller)
  rescue NameError => e
    msg = "Controller class not found for controller `#{path}'"
    Merb.logger.warn!(msg)
    raise ControllerExceptions::NotFound, msg
  end
end

#domain(tld_length = 1) ⇒ Object

Parameters

tld_length<Fixnum>

Number of domains levels to inlclude in the top level domain. Defaults to 1.

Returns

String

The full domain name without the port number.



489
490
491
# File 'lib/merb-core/dispatch/request.rb', line 489

def domain(tld_length = 1)
  host.split('.').last(1 + tld_length).join('.').sub(/:\d+$/,'')
end

#find_route!Object

Find route using requested URI and merges route parameters (:action, :controller and named segments) into request params hash.



96
97
98
99
# File 'lib/merb-core/dispatch/request.rb', line 96

def find_route!
  @route, @route_params = Merb::Router.route_for(self)
  params.merge! @route_params
end

#full_uriObject

Returns

String

The full URI, including protocol and host



336
337
338
# File 'lib/merb-core/dispatch/request.rb', line 336

def full_uri
  protocol + "://" + host + uri
end

#gatewayObject

Returns

String

The gateway.



408
409
410
# File 'lib/merb-core/dispatch/request.rb', line 408

def gateway
  @env['GATEWAY_INTERFACE']
end

#handleObject



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
# File 'lib/merb-core/dispatch/dispatcher.rb', line 39

def handle
  start = Time.now
  Merb.logger.info "Started request handling: #{start.to_s}"
  
  find_route!
  return redirect if redirects?
  
  klass = controller
  Merb.logger.debug("Routed to: #{params.inspect}")
  
  unless klass < Controller
    raise NotFound, 
      "Controller '#{klass}' not found.\n" \
      "If Merb tries to find a controller for static files, " \
      "you may need to check your Rackup file, see the Problems " \
      "section at: http://wiki.merbivore.com/pages/rack-middleware"
  end
  
  if klass.abstract?
    raise NotFound, "The '#{klass}' controller has no public actions"
  end

  controller = dispatch_action(klass, params[:action])
  controller._benchmarks[:dispatch_time] = Time.now - start
  Merb.logger.info controller._benchmarks.inspect
  Merb.logger.flush
  controller
rescue Object => exception
  dispatch_exception(exception)
end

#hostObject

Returns

String

The full hostname including the port.



466
467
468
# File 'lib/merb-core/dispatch/request.rb', line 466

def host
  @env['HTTP_X_FORWARDED_HOST'] || @env['HTTP_HOST'] 
end

#if_modified_sinceObject

Returns

Value of If-Modified-Since request header.



501
502
503
504
505
# File 'lib/merb-core/dispatch/request.rb', line 501

def if_modified_since
  if time = @env[Merb::Const::HTTP_IF_MODIFIED_SINCE]
    Time.rfc2822(time)
  end
end

#if_none_matchObject

Returns

Value of If-None-Match request header.



495
496
497
# File 'lib/merb-core/dispatch/request.rb', line 495

def if_none_match
  @env[Merb::Const::HTTP_IF_NONE_MATCH]
end

#keep_aliveObject

Returns

String

Value of HTTP_KEEP_ALIVE.



390
391
392
# File 'lib/merb-core/dispatch/request.rb', line 390

def keep_alive
  @env['HTTP_KEEP_ALIVE']
end

#matched!Object

Sets the request as matched. This will abort evaluating any further deferred procs.




132
133
134
# File 'lib/merb-core/dispatch/request.rb', line 132

def matched!
  @matched = true
end

#matched?Boolean

Checks whether or not the request has been matched to a route.


Returns:

  • (Boolean)


139
140
141
# File 'lib/merb-core/dispatch/request.rb', line 139

def matched?
  @matched
end

#messageObject



269
270
271
272
273
274
275
276
# File 'lib/merb-core/dispatch/request.rb', line 269

def message
  return {} unless params[:_message]
  begin
    Marshal.load(Merb::Request.unescape(params[:_message]).unpack("m").first)
  rescue ArgumentError
    {}
  end
end

#methodObject

Returns

<Symbol>

The name of the request method, e.g. :get.

Notes

If the method is post, then the blocks specified in http_method_overrides will be checked for the masquerading method. The block will get the controller yielded to it. The first matching workaround wins. To disable this behavior, set http_method_overrides = []



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/merb-core/dispatch/request.rb', line 68

def method
  @method ||= begin
    request_method = @env['REQUEST_METHOD'].downcase.to_sym
    case request_method
    when :get, :head, :put, :delete, :options
      request_method
    when :post
      m = nil
      self.class.http_method_overrides.each do |o|
        m ||= o.call(self); break if m
      end
      m.downcase! if m
      METHODS.include?(m) ? m.to_sym : :post
    else
      raise "Unknown REQUEST_METHOD: #{@env['REQUEST_METHOD']}"
    end
  end
end

#paramsObject

Returns

Mash

All request parameters.

Notes

The order of precedence for the params is XML, JSON, multipart, body and request string.



259
260
261
262
263
264
265
266
267
# File 'lib/merb-core/dispatch/request.rb', line 259

def params
  @params ||= begin
    h = body_and_query_params.merge(route_params)      
    h.merge!(multipart_params) if self.class.parse_multipart_params && multipart_params
    h.merge!(json_params) if self.class.parse_json_params && json_params
    h.merge!(xml_params) if self.class.parse_xml_params && xml_params
    h
  end
end

#pathObject

Returns

String

The URI without the query string. Strips trailing “/” and reduces duplicate “/” to a single “/”.



446
447
448
449
450
# File 'lib/merb-core/dispatch/request.rb', line 446

def path
  path = (uri.empty? ? '/' : uri.split('?').first).squeeze("/")
  path = path[0..-2] if (path[-1] == ?/) && path.size > 1
  path
end

#path_infoObject

Returns

String

The path info.



454
455
456
# File 'lib/merb-core/dispatch/request.rb', line 454

def path_info
  @path_info ||= self.class.unescape(@env['PATH_INFO'])
end

#portObject

Returns

Fixnum

The server port.



460
461
462
# File 'lib/merb-core/dispatch/request.rb', line 460

def port
  @env['SERVER_PORT'].to_i
end

#protocolObject

Returns

String

The protocol, i.e. either “https” or “http” depending on the HTTPS header.



318
319
320
# File 'lib/merb-core/dispatch/request.rb', line 318

def protocol
  ssl? ? 'https' : 'http'
end

#query_stringObject

Returns

String

The query string.



426
427
428
# File 'lib/merb-core/dispatch/request.rb', line 426

def query_string
  @env['QUERY_STRING']  
end

#raw_postObject

Returns

String

The raw post.



285
286
287
288
# File 'lib/merb-core/dispatch/request.rb', line 285

def raw_post
  @body.rewind if @body.respond_to?(:rewind)
  @raw_post ||= @body.read
end

#redirectObject

Set up a faux controller to do redirection from the router

Parameters

request<Merb::Request>

The Merb::Request object that was created in #handle

status<Integer>

The status code to return with the controller

url<String>

The URL to return

Example

r.match(“/my/old/crusty/url”).redirect(“example.com/index.html”)

Returns

Merb::Controller

Merb::Controller set with redirect headers and a 301/302 status



86
87
88
89
90
91
92
93
94
95
96
# File 'lib/merb-core/dispatch/dispatcher.rb', line 86

def redirect
  status, url = redirect_status, redirect_url
  controller = Merb::Controller.new(self, status)

  Merb.logger.info("Dispatcher redirecting to: #{url} (#{status})")
  Merb.logger.flush
  
  controller.headers['Location'] = url
  controller.body = "<html><body>You are being <a href=\"#{url}\">redirected</a>.</body></html>"
  controller
end

#redirect_statusObject

Redirect status of route matched this request.

Returns

Integer

The URL to redirect to if the route redirects



148
149
150
# File 'lib/merb-core/dispatch/request.rb', line 148

def redirect_status
  @route_params[:status] if redirects?
end

#redirect_urlObject

Returns redirect url of route matched this request.

Returns

<String>

redirect url of route matched this request



156
157
158
# File 'lib/merb-core/dispatch/request.rb', line 156

def redirect_url
  @route_params[:url] if redirects?
end

#redirects!Object

Sets the request as a redirect. This method is only really used in the router to tell the request object how to handle the route params. This will also set the request as matched.




123
124
125
126
# File 'lib/merb-core/dispatch/request.rb', line 123

def redirects!
  @matched   = true
  @redirects = true
end

#redirects?Boolean

Returns true if matched route does immediate redirection.

Returns

<Boolean>

if matched route does immediate redirection.

Returns:

  • (Boolean)


164
165
166
# File 'lib/merb-core/dispatch/request.rb', line 164

def redirects?
  @redirects
end

#refererObject

Returns

String

The HTTP referer.



330
331
332
# File 'lib/merb-core/dispatch/request.rb', line 330

def referer
  @env['HTTP_REFERER']
end

#remote_ipObject

Returns

String

The remote IP address.



300
301
302
303
304
305
306
307
308
309
310
311
312
# File 'lib/merb-core/dispatch/request.rb', line 300

def remote_ip
  return @env['HTTP_CLIENT_IP'] if @env.include?('HTTP_CLIENT_IP')

  if @env.include?(Merb::Const::HTTP_X_FORWARDED_FOR) then
    remote_ips = @env[Merb::Const::HTTP_X_FORWARDED_FOR].split(',').reject do |ip|
      ip =~ /^unknown$|^(127|10|172\.16|192\.168)\./i
    end

    return remote_ips.first.strip unless remote_ips.empty?
  end

  return @env[Merb::Const::REMOTE_ADDR]
end

#reset_params!Object

Resets the params to a nil value.



279
280
281
# File 'lib/merb-core/dispatch/request.rb', line 279

def reset_params!
  @params = nil
end

#script_nameObject

Returns

String

The script name.



366
367
368
# File 'lib/merb-core/dispatch/request.rb', line 366

def script_name
  @env['SCRIPT_NAME']
end

#server_nameObject

Returns

String

The server name.



354
355
356
# File 'lib/merb-core/dispatch/request.rb', line 354

def server_name
  @env['SERVER_NAME']
end

#server_softwareObject

Returns

String

The server software.



384
385
386
# File 'lib/merb-core/dispatch/request.rb', line 384

def server_software
  @env['SERVER_SOFTWARE']
end

#ssl?Boolean

Returns

Boolean:

True if the request is an SSL request.

Returns:

  • (Boolean)


324
325
326
# File 'lib/merb-core/dispatch/request.rb', line 324

def ssl?
  @env['HTTPS'] == 'on' || @env['HTTP_X_FORWARDED_PROTO'] == 'https'
end

#subdomains(tld_length = 1) ⇒ Object

Parameters

tld_length<Fixnum>

Number of domains levels to inlclude in the top level domain. Defaults to 1.

Returns

Array

All the subdomain parts of the host.



477
478
479
480
# File 'lib/merb-core/dispatch/request.rb', line 477

def subdomains(tld_length = 1)
  parts = host.split('.')
  parts[0..-(tld_length+2)]
end

#uriObject

Returns

String

The request URI.



342
343
344
# File 'lib/merb-core/dispatch/request.rb', line 342

def uri
  @env['REQUEST_PATH'] || @env['REQUEST_URI'] || path_info
end

#user_agentObject

Returns

String

The HTTP user agent.



348
349
350
# File 'lib/merb-core/dispatch/request.rb', line 348

def user_agent
  @env['HTTP_USER_AGENT']
end

#versionObject

Returns

String

The HTTP version



402
403
404
# File 'lib/merb-core/dispatch/request.rb', line 402

def version
  @env['HTTP_VERSION']
end

#xml_http_request?Boolean Also known as: xhr?, ajax?

Returns

Boolean

If the request is an XML HTTP request.

Returns:

  • (Boolean)


292
293
294
# File 'lib/merb-core/dispatch/request.rb', line 292

def xml_http_request?
  not /XMLHttpRequest/i.match(@env['HTTP_X_REQUESTED_WITH']).nil?
end