Class: Arrow::Transaction

Inherits:
Object
  • Object
show all
Extended by:
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

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

Constant Summary collapse

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

<<-"EOF".gsub(/^\t/, '')
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html>
  <head><title>%d %s</title></head>
  <body><h1>%s</h1><p>%s</p></body>
</html>
EOF
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

Constants::HTML_MIMETYPE, Constants::RUBY_MARSHALLED_MIMETYPE, Constants::RUBY_OBJECT_MIMETYPE, Constants::XHTML_MIMETYPE, Constants::YAML_DOMAIN

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from Object

deprecate_class_method, deprecate_method, inherited

Constructor Details

#initialize(request, config, broker) ⇒ Transaction

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.



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

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

#applet_pathObject

The applet portion of the path_info



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

def applet_path
  @applet_path
end

#brokerObject (readonly)

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



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

def broker
  @broker
end

#configObject (readonly)

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



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

def config
  @config
end

#cookiesObject (readonly)

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



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

def cookies
  @cookies
end

#dataObject (readonly)

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



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

def data
  @data
end

#handler_statusObject

The handler status code to return to Apache



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

def handler_status
  @handler_status
end

#requestObject (readonly)

The Apache::Request that initiated this transaction



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

def request
  @request
end

#request_cookiesObject (readonly)

The Hash of Arrow::Cookies parsed from the request



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

def request_cookies
  @request_cookies
end

#serialObject (readonly)

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



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

def serial
  @serial
end

#vargsObject

The argument validator (a FormValidator object)



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

def vargs
  @vargs
end

Instance Method Details

#accepted_typesObject

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



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

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

#accepts?(content_type) ⇒ Boolean Also known as: accept?

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

Returns:

  • (Boolean)


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

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

#accepts_html?Boolean

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

Returns:

  • (Boolean)


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

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.



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

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

#app_rootObject 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.



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

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

#app_root_urlObject Also known as: approot_url

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



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

def app_root_url
	return construct_url( self.app_root )
end

#appletObject Also known as: applet_uri

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



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

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

#applet_urlObject

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



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

def applet_url
	return construct_url( self.applet )
end

#arrow_versionObject

Get the verson of Arrow currently running.



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

def arrow_version
	return Arrow::VERSION
end

#attachment=(filename) ⇒ Object

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



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

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

#browser_is_ie?Boolean

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

Returns:

  • (Boolean)


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

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

#construct_url(uri) ⇒ Object

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.



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

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

#explicitly_accepts?(content_type) ⇒ Boolean Also known as: explicitly_accept?

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

Returns:

  • (Boolean)


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

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

#for_ie_usersObject

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



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

def for_ie_users
	yield if self.browser_is_ie?
end

#form_request?Boolean

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)


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

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

#inspectObject

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



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

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

#is_ajax_request?Boolean

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

Returns:

  • (Boolean)


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

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

#is_declined?Boolean

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

Returns:

  • (Boolean)


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

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

#is_success?Boolean

Returns true if the transactions response status is 2xx.

Returns:

  • (Boolean)


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

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

#normalized_accept_stringObject

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



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

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

#not_modifiedObject

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



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

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

#parsed_uriObject

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



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

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

#pathObject

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



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

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

#proxied_hostObject

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.



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

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

#redirect(uri, status_code = Apache::HTTP_MOVED_TEMPORARILY) ⇒ Object

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).



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

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

#refererObject

Get the request’s referer, if any



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

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

#referring_actionObject

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.



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

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

#referring_appletObject

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.



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

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

#refresh(seconds, url = nil) ⇒ Object

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



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

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

#remote_ipObject

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



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

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

#request_content_typeObject

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



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

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

#root_dispatcher?Boolean

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

Returns:

  • (Boolean)


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

def root_dispatcher?
	return @root_dispatcher
end

#session(config = {}) ⇒ Object

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



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

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

#session?Boolean

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

Returns:

  • (Boolean)


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

def session?
	@session ? true : false
end

#status_doc(status_code, uri = nil) ⇒ Object

Return a minimal HTML doc for representing a given status_code



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

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