Class: Arrow::Transaction

Inherits:
Object
  • Object
show all
Extends:
Forwardable
Includes:
Constants
Defined in:
lib/arrow/transaction.rb

Overview

The Arrow::Transaction class, a derivative of Arrow::Object. Instances of this class encapsulate a transaction within a web application implemented using the Arrow application framework.

Authors

  • Michael Granger

Please see the file LICENSE in the top-level directory for licensing details.

Constant Summary

FORM_CONTENT_TYPES =

Regex to match the mimetypes that browsers use for sending form data

%r{application/x-www-form-urlencoded|multipart/form-data}i
HTML_DOC =

A minimal HTML document for #status_doc

"<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n<html>\n<head><title>%d %s</title></head>\n<body><h1>%s</h1><p>%s</p></body>\n</html>\n".gsub(/^\t/, '')
STATUS_NAME =

Names for redirect statuses for #status_doc

{
  300   => "Multiple Choices",
  301   => "Moved Permanently",
  302   => "Found",
  303   => "See Other",
  304   => "Not Modified",
  305   => "Use Proxy",
  307   => "Temporary Redirect",
}
DelegatedMethods =
Apache::Request.instance_methods(false) - [
  "inspect", "to_s"
]

Constants included from Constants

HTML_MIMETYPE, RUBY_MARSHALLED_MIMETYPE, RUBY_OBJECT_MIMETYPE, XHTML_MIMETYPE, YAML_DOMAIN

Instance Attribute Summary

Instance Method Summary

Methods inherited from Object

deprecate_class_method, deprecate_method, inherited

Methods included from Loggable

#log

Constructor Details

- (Transaction) initialize(request, config, broker)

Create a new Arrow::Transaction object with the specified request (an Apache::Request object), config (an Arrow::Config object), broker object (an Arrow::Broker), and session (Arrow::Session) objects.



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
# File 'lib/arrow/transaction.rb', line 77

def initialize( request, config, broker )
  @request         = request
  @config          = config
  @broker          = broker
  @handler_status  = Apache::OK

  @serial          = make_transaction_serial( request )

  # Stuff that may be filled in later
  @session         = nil # Lazily-instantiated
  @applet_path     = nil # Added by the broker
  @vargs           = nil # Filled in by the applet
  @data            = {}
  @request_cookies = parse_cookies( request )
  @cookies         = Arrow::CookieSet.new()
  @accepted_types  = nil

  # Check for a "RubyOption root_dispatcher true"
  if @request.options.key?('root_dispatcher') &&
    @request.options['root_dispatcher'].match( /^(true|yes|1)$/i )
    self.log.debug "Dispatching from root path"
    @root_dispatcher = true
  else
    self.log.debug "Dispatching from sub-path"
    @root_dispatcher = false
  end

  @request.sync_header = true
  super()
end

Instance Attribute Details

- (Object) applet_path

The applet portion of the path_info



132
133
134
# File 'lib/arrow/transaction.rb', line 132

def applet_path
  @applet_path
end

- (Object) broker (readonly)

The Arrow::Broker that is responsible for delegating the Transaction to one or more Arrow::Applet objects.



126
127
128
# File 'lib/arrow/transaction.rb', line 126

def broker
  @broker
end

- (Object) config (readonly)

The Arrow::Config object for the Arrow application that created this transaction.



122
123
124
# File 'lib/arrow/transaction.rb', line 122

def config
  @config
end

- (Object) cookies (readonly)

The Arrow::CookieSet that contains cookies to be added to the response



144
145
146
# File 'lib/arrow/transaction.rb', line 144

def cookies
  @cookies
end

- (Object) data (readonly)

User-data hash. Can be used to pass data between applets in a chain.



138
139
140
# File 'lib/arrow/transaction.rb', line 138

def data
  @data
end

- (Object) handler_status

The handler status code to return to Apache



147
148
149
# File 'lib/arrow/transaction.rb', line 147

def handler_status
  @handler_status
end

- (Object) request (readonly)

The Apache::Request that initiated this transaction



118
119
120
# File 'lib/arrow/transaction.rb', line 118

def request
  @request
end

- (Object) request_cookies (readonly)

The Hash of Arrow::Cookies parsed from the request



141
142
143
# File 'lib/arrow/transaction.rb', line 141

def request_cookies
  @request_cookies
end

- (Object) serial (readonly)

The transaction’s unique id in the context of the system.



135
136
137
# File 'lib/arrow/transaction.rb', line 135

def serial
  @serial
end

- (Object) vargs

The argument validator (a FormValidator object)



129
130
131
# File 'lib/arrow/transaction.rb', line 129

def vargs
  @vargs
end

Instance Method Details

- (Object) accepted_types

Return the contents of the ‘Accept’ header as an Array of Arrow::AcceptParam objects.



400
401
402
403
# File 'lib/arrow/transaction.rb', line 400

def accepted_types
  @accepted_types ||= parse_accept_header( self.headers_in['Accept'] )
  return @accepted_types
end

- (Boolean) accepts?(content_type) Also known as: accept?

Returns boolean true/false if the requestor can handle the given content_type.

Returns:

  • (Boolean)


408
409
410
# File 'lib/arrow/transaction.rb', line 408

def accepts?( content_type )
  return self.accepted_types.find {|type| type =~ content_type } ? true : false
end

- (Boolean) accepts_html?

Returns true if the request’s content-negotiation headers indicate that it can accept either ‘text/html’ or ‘application/xhtml+xml’

Returns:

  • (Boolean)


425
426
427
# File 'lib/arrow/transaction.rb', line 425

def accepts_html?
  return self.accepts?( XHTML_MIMETYPE ) || self.accepts?( HTML_MIMETYPE )
end

Add a ‘Set-Cookie’ header to the response for each cookie that currently exists the transaction’s cookieset.



312
313
314
315
316
317
318
319
320
321
322
323
324
# File 'lib/arrow/transaction.rb', line 312

def add_cookie_headers
  self.cookies.each do |cookie|
    if self.is_success?
      self.log.debug "Adding 'Set-Cookie' header: %p (%p)" % 
        [cookie, cookie.to_s]
      self.headers_out['Set-Cookie'] = cookie.to_s
    else
      self.log.debug "Adding 'Set-Cookie' to the error headers: %p (%p)" %
        [cookie, cookie.to_s]
      self.err_headers_out['Set-Cookie'] = cookie.to_s
    end
  end
end

- (Object) app_root Also known as: approot

Return the portion of the request’s URI that serves as the base URI for the application. All self-referential URLs created by the application should include this.



235
236
237
238
# File 'lib/arrow/transaction.rb', line 235

def app_root
  return "" if self.root_dispatcher?
  return @request.script_name
end

- (Object) app_root_url Also known as: approot_url

Returns a fully-qualified URI String to the current applet using the request object’s server name and port.



244
245
246
# File 'lib/arrow/transaction.rb', line 244

def app_root_url
  return construct_url( self.app_root )
end

- (Object) applet Also known as: applet_uri

Return an absolute uri that refers back to the applet the transaction is being run in



252
253
254
# File 'lib/arrow/transaction.rb', line 252

def applet
  return [ self.app_root, self.applet_path ].join("/").gsub( %r{//+}, '/' )
end

- (Object) applet_url

Returns a fully-qualified URI String to the current applet using the request object’s server name and port.



261
262
263
# File 'lib/arrow/transaction.rb', line 261

def applet_url
  return construct_url( self.applet )
end

- (Object) arrow_version

Get the verson of Arrow currently running.



541
542
543
# File 'lib/arrow/transaction.rb', line 541

def arrow_version
  return Arrow::VERSION
end

- (Object) attachment=(filename)

Set the result’s ‘Content-Disposition’ header to ‘attachment’ and set the attachment’s filename.



371
372
373
374
375
376
377
378
379
380
# File 'lib/arrow/transaction.rb', line 371

def attachment=( filename )

  # IE flubs attachments of any mimetype it handles directly.
  if self.browser_is_ie?
    self.content_type = 'application/octet-stream'
  end

  val = %q{attachment; filename="%s"} % [ filename ]
  self.headers_out['Content-Disposition'] = val
end

- (Boolean) browser_is_ie?

Returns true if the User-Agent header indicates that the remote browser is Internet Explorer. Useful for making the inevitable IE workarounds.

Returns:

  • (Boolean)


443
444
445
446
# File 'lib/arrow/transaction.rb', line 443

def browser_is_ie?
  agent = self.headers_in['user-agent'] || ''
  return agent =~ /MSIE/ ? true : false
end

- (Object) construct_url(uri)

Overridden from Apache::Request to take Apache mod_proxy headers into account. If the ‘X-Forwarded-Host’ or ‘X-Forwarded-Server’ headers exist in the request, the hostname specified is used instead of the canonical host.



331
332
333
334
335
336
337
338
339
340
341
342
343
# File 'lib/arrow/transaction.rb', line 331

def construct_url( uri )
  url = @request.construct_url( uri )

  # If the request came through a proxy, rewrite the url's host to match
  # the hostname the proxy is forwarding for.
  if (( host = self.proxied_host ))
    uriobj = URI.parse( url )
    uriobj.host = host
    url = uriobj.to_s
  end

  return url
end

- (Boolean) explicitly_accepts?(content_type) Also known as: explicitly_accept?

Returns boolean true/false if the requestor can handle the given content_type, not including mime wildcards.

Returns:

  • (Boolean)


416
417
418
419
# File 'lib/arrow/transaction.rb', line 416

def explicitly_accepts?( content_type )
  self.accepted_types.reject { |param| param.subtype.nil? }.
    find {|type| type =~ content_type } ? true : false
end

- (Object) for_ie_users

Execute a block if the User-Agent header indicates that the remote browser is Internet Explorer. Useful for making the inevitable IE workarounds.



452
453
454
# File 'lib/arrow/transaction.rb', line 452

def for_ie_users
  yield if self.browser_is_ie?
end

- (Boolean) form_request?

Return true if there are HTML form parameters in the request, either in the query string with a GET request, or in the body of a POST with a mimetype of either ‘application/x-www-form-urlencoded’ or ‘multipart/form-data’.

Returns:

  • (Boolean)


469
470
471
472
473
474
475
476
477
478
479
480
481
# File 'lib/arrow/transaction.rb', line 469

def form_request?
  case self.request_method
  when 'GET', 'HEAD', 'DELETE', 'PUT'
    return (!self.parsed_uri.query.nil? || 
      self.request_content_type =~ FORM_CONTENT_TYPES) ? true : false

  when 'POST'
    return self.request_content_type =~ FORM_CONTENT_TYPES ? true : false

  else
    return false
  end
end

- (Object) inspect

Returns a human-readable String representation of the transaction, suitable for debugging.



152
153
154
155
156
157
158
159
# File 'lib/arrow/transaction.rb', line 152

def inspect
  "#<%s:0x%0x serial: %s; HTTP status: %d>" % [
    self.class.name,
    self.object_id * 2,
    self.serial,
    self.status
  ]
end

- (Boolean) is_ajax_request?

Return true if the request is from XMLHttpRequest (as indicated by the ‘X-Requested-With’ header from Scriptaculous or jQuery)

Returns:

  • (Boolean)


459
460
461
462
463
# File 'lib/arrow/transaction.rb', line 459

def is_ajax_request?
  xrw_header = self.headers_in['x-requested-with']
  return true if !xrw_header.nil? && xrw_header =~ /xmlhttprequest/i
  return false
end

- (Boolean) is_declined?

Returns true if the transaction’s server status will cause the request to be declined (i.e., not handled by Arrow)

Returns:

  • (Boolean)


183
184
185
186
187
# File 'lib/arrow/transaction.rb', line 183

def is_declined?
  self.log.debug "Checking to see if the transaction is declined (%p)" %
    [self.handler_status]
  return self.handler_status == Apache::DECLINED ? true : false
end

- (Boolean) is_success?

Returns true if the transactions response status is 2xx.

Returns:

  • (Boolean)


175
176
177
178
# File 'lib/arrow/transaction.rb', line 175

def is_success?
  return nil unless self.status
  return (self.status / 100) == 2
end

- (Object) normalized_accept_string

Return a normalized list of acceptable types, sorted by q-value and specificity.



431
432
433
# File 'lib/arrow/transaction.rb', line 431

def normalized_accept_string
  return self.accepted_types.sort.collect {|ap| ap.to_s }.join( ', ' )
end

- (Object) not_modified

Set the necessary header fields in the response to cause a NOT_MODIFIED response to be sent.



522
523
524
# File 'lib/arrow/transaction.rb', line 522

def not_modified
  return self.redirect( uri, Apache::HTTP_NOT_MODIFIED )
end

- (Object) parsed_uri

Return a URI object that is parsed from the request’s URI.



384
385
386
# File 'lib/arrow/transaction.rb', line 384

def parsed_uri
  return URI.parse( self.request.unparsed_uri )
end

- (Object) path

Returns the path operated on by the Arrow::Broker when delegating the transaction. Equal to the #uri minus the #app_root.



225
226
227
228
229
# File 'lib/arrow/transaction.rb', line 225

def path
  path = @request.uri
  uripat = Regexp.new( "^" + self.app_root )
  return path.sub( uripat, '' )
end

- (Object) proxied_host

If the request came from a reverse proxy (i.e., the X-Forwarded-Host or X-Forwarded-Server headers are present), return the hostname that the proxy is forwarding for. If no proxy headers are present, return nil.



350
351
352
353
# File 'lib/arrow/transaction.rb', line 350

def proxied_host
  headers = @request.headers_in
  return headers['x-forwarded-host'] || headers['x-forwarded-server']
end

- (Object) redirect(uri, status_code = Apache::HTTP_MOVED_TEMPORARILY)

Set the necessary fields in the request to cause the response to be a redirect to the given url with the specified status_code (302 by default).



510
511
512
513
514
515
516
517
# File 'lib/arrow/transaction.rb', line 510

def redirect( uri, status_code=Apache::HTTP_MOVED_TEMPORARILY )
  self.log.debug "Redirecting to %s" % uri
  self.headers_out[ 'Location' ] = uri.to_s
  self.status = status_code
  self.handler_status = Apache::REDIRECT

  return ''
end

- (Object) referer

Get the request’s referer, if any



364
365
366
# File 'lib/arrow/transaction.rb', line 364

def referer
  return self.headers_in['Referer']
end

- (Object) referring_action

If the referer was another applet under the same Arrow instance, return the name of the action that preceded the current one. If there was no ‘Referer’ header, or the referer wasn’t an applet under the same Arrow instance, return nil.



289
290
291
292
293
294
295
296
297
298
299
300
301
302
# File 'lib/arrow/transaction.rb', line 289

def referring_action
  return nil unless self.referer
  uri = URI.parse( self.referer )
  path = uri.path or return nil
  appletRe = Regexp.new( self.app_root + "/\\w+/" )

  return nil unless appletRe.match( path )
  subpath = path.
    sub( appletRe, '' ).
    split( %r{/} ).
    first

  return subpath
end

- (Object) referring_applet

If the referer was another applet under the same Arrow instance, return the uri to it. If there was no ‘Referer’ header, or the referer wasn’t an applet under the same Arrow instance, returns nil.



269
270
271
272
273
274
275
276
277
278
279
280
281
282
# File 'lib/arrow/transaction.rb', line 269

def referring_applet
  return nil unless self.referer
  uri = URI.parse( self.referer )
  path = uri.path or return nil
  rootRe = Regexp.new( self.app_root + "/" )

  return nil unless rootRe.match( path )
  subpath = path.
    sub( rootRe, '' ).
    split( %r{/} ).
    first

  return subpath
end

- (Object) refresh(seconds, url = nil)

Set the necessary header to make the displayed page refresh to the specified url in the given number of seconds.



529
530
531
532
533
534
535
536
537
# File 'lib/arrow/transaction.rb', line 529

def refresh( seconds, url=nil )
  seconds = Integer( seconds )
  url ||= self.construct_url( '' )
  if !URI.parse( url ).absolute?
    url = self.construct_url( url )
  end

  self.headers_out['Refresh'] = "%d;%s" % [seconds, url]
end

- (Object) remote_ip

Fetch the client’s IP, either from proxy headers or the connection’s IP.



357
358
359
# File 'lib/arrow/transaction.rb', line 357

def remote_ip
  return self.headers_in['X-Forwarded-For'] || self.connection.remote_ip
end

- (Object) request_content_type

Return the Content-type header given in the request’s headers, if any



390
391
392
# File 'lib/arrow/transaction.rb', line 390

def request_content_type
  return self.headers_in['Content-type']
end

- (Boolean) root_dispatcher?

Returns true if the dispatcher is mounted on the root URI (“/”)

Returns:

  • (Boolean)


218
219
220
# File 'lib/arrow/transaction.rb', line 218

def root_dispatcher?
  return @root_dispatcher
end

- (Object) session(config = {})

The session associated with the receiver (an Arrow::Session object).



169
170
171
# File 'lib/arrow/transaction.rb', line 169

def session( config={} )
  @session ||= Arrow::Session.create( self, config )
end

- (Boolean) session?

Returns true if a session has been created for the receiver.

Returns:

  • (Boolean)


163
164
165
# File 'lib/arrow/transaction.rb', line 163

def session?
  @session ? true : false
end

- (Object) status_doc(status_code, uri = nil)

Return a minimal HTML doc for representing a given status_code



489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
# File 'lib/arrow/transaction.rb', line 489

def status_doc( status_code, uri=nil )
  body = ''
  if uri
    body = %q{<a href="%s">%s</a>} % [ uri, uri ]
  end

  #<head><title>%d %s</title></head>
  #<body><h1>%s</h1><p>%s</p></body>
  return HTML_DOC % [
    status_code,
    STATUS_NAME[status_code],
    STATUS_NAME[status_code],
    body
  ]
end