Class: Dnsruby::Resolver
- Inherits:
-
Object
- Object
- Dnsruby::Resolver
- Defined in:
- lib/Dnsruby/Resolver.rb
Overview
Description
This class uses a set of SingleResolvers to perform queries with retries across multiple nameservers.
The retry policy is a combination of the Net::DNS and dnsjava approach, and has the option of :
-
A total timeout for the query (defaults to 0, meaning “no total timeout”)
-
A retransmission system that targets the namervers concurrently once the first query round is
complete, but in which the total time per query round is split between the number of nameservers
targetted for the first round. and total time for query round is doubled for each query round
Note that, if a total timeout is specified, then that will apply regardless of the retry policy
(i.e. it may cut retries short).
Note also that these timeouts are distinct from the SingleResolver's packet_timeout
Methods
Synchronous
These methods raise an exception or return a response message with rcode==NOERROR
-
Dnsruby::Resolver#send_message(msg)
-
Dnsruby::Resolver#query(name [, type [, klass]])
Asynchronous
These methods use a response queue to return the response and the error
-
Dnsruby::Resolver#send_async(msg, query_id, response_queue)
Constant Summary collapse
- DefaultQueryTimeout =
0- DefaultPacketTimeout =
10- DefaultRetryTimes =
4- DefaultRetryDelay =
5- DefaultPort =
53- DefaultUDPSize =
512
Instance Attribute Summary collapse
-
#config ⇒ Object
readonly
The current Config.
-
#ignore_truncation ⇒ Object
Should truncation be ignored? i.e.
-
#packet_timeout ⇒ Object
The timeout for any individual packet.
-
#persistent_tcp ⇒ Object
Should TCP queries be sent on a persistent socket?.
-
#persistent_udp ⇒ Object
Should UDP queries be sent on a persistent socket?.
-
#port ⇒ Object
The port to send queries to on the resolver.
-
#query_timeout ⇒ Object
Note that this timeout represents the total time a query may run for - multiple packets can be sent to multiple nameservers in this time.
-
#recurse ⇒ Object
Should the Recursion Desired bit be set?.
-
#retry_delay ⇒ Object
The query will be tried across nameservers retry_times times, with a delay of retry_delay seconds between each retry.
-
#retry_times ⇒ Object
The query will be tried across nameservers retry_times times, with a delay of retry_delay seconds between each retry.
-
#single_resolvers ⇒ Object
readonly
The array of SingleResolvers used for sending query messages.
-
#src_address ⇒ Object
The source address to send queries from.
-
#src_port ⇒ Object
The source port to send queries from.
-
#tsig_key ⇒ Object
Returns the value of attribute tsig_key.
-
#udp_size ⇒ Object
The maximum UDP size to be used.
-
#use_tcp ⇒ Object
Should TCP be used as a transport rather than UDP?.
Instance Method Summary collapse
- #add_config_nameservers ⇒ Object
-
#add_resolver(single) ⇒ Object
Add a new SingleResolver to the list of resolvers this Resolver object will query.
-
#close ⇒ Object
Close the Resolver.
-
#generate_timeouts ⇒ Object
:nodoc: all.
-
#handle_error_response(select_queue, query_id, error, response) ⇒ Object
:nodoc: all.
-
#handle_queue_event(queue, id) ⇒ Object
This method is called by the SelectThread (in the select thread) when the queue has a new item on it.
-
#handle_response(select_queue, query_id, response) ⇒ Object
:nodoc: all.
-
#initialize(*args) ⇒ Resolver
constructor
Create a new Resolver object.
- #nameserver=(n) ⇒ Object
-
#query(name, type = Types.A, klass = Classes.IN) ⇒ Object
Query for a n.
-
#reset_attributes ⇒ Object
:nodoc: all.
-
#send_async(msg, client_query_id, client_queue) ⇒ Object
Asynchronously sends a DNS packet (Dnsruby::Message).
-
#send_message(message) ⇒ Object
Send a message, and wait for the response.
-
#send_result_and_close(client_queue, client_query_id, select_queue, msg, error) ⇒ Object
MUST BE CALLED IN A SYNCHRONIZED BLOCK! .
- #set_config_nameserver(n) ⇒ Object
-
#tick ⇒ Object
This method is called ten times a second from the select loop, in the select thread.
-
#update ⇒ Object
:nodoc: all.
Constructor Details
#initialize(*args) ⇒ Resolver
Create a new Resolver object. If no parameters are passed in, then the default system configuration will be used. Otherwise, a Hash may be passed in with the following optional elements :
-
:port
-
:use_tcp
-
:tsig_key
-
:ignore_truncation
-
:src_address
-
:src_port
-
:persistent_tcp
-
:persistent_udp
-
:recurse
-
:udp_size
-
:config_info - see Config
-
:nameserver - can be either a String or an array of Strings
-
:packet_timeout
-
:query_timeout
-
:retry_times
-
:retry_delay
467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 |
# File 'lib/Dnsruby/Resolver.rb', line 467 def initialize(*args) reset_attributes # Process args if (args.length==1) if (args[0].class == Hash) args[0].keys.each do |key| begin if (key == :config_info) @config.set_config_info(args[0][:config_info]) elsif (key==:nameserver) set_config_nameserver(args[0][:nameserver]) else send(key.to_s+"=", args[0][key]) end rescue Exception TheLog.error("Argument #{key} not valid\n") end end elsif (args[0].class == Config) # also accepts a Config object from Dnsruby::Resolv @config = args[0] end else #@TODO@ ? end if (@single_resolvers==[]) add_config_nameservers end update end |
Instance Attribute Details
#config ⇒ Object (readonly)
The current Config
83 84 85 |
# File 'lib/Dnsruby/Resolver.rb', line 83 def config @config end |
#ignore_truncation ⇒ Object
Should truncation be ignored? i.e. the TC bit is ignored and thus the resolver will not requery over TCP if TC is set
64 65 66 |
# File 'lib/Dnsruby/Resolver.rb', line 64 def ignore_truncation @ignore_truncation end |
#packet_timeout ⇒ Object
The timeout for any individual packet. This is the timeout used by SingleResolver
89 90 91 |
# File 'lib/Dnsruby/Resolver.rb', line 89 def packet_timeout @packet_timeout end |
#persistent_tcp ⇒ Object
Should TCP queries be sent on a persistent socket?
72 73 74 |
# File 'lib/Dnsruby/Resolver.rb', line 72 def persistent_tcp @persistent_tcp end |
#persistent_udp ⇒ Object
Should UDP queries be sent on a persistent socket?
74 75 76 |
# File 'lib/Dnsruby/Resolver.rb', line 74 def persistent_udp @persistent_udp end |
#port ⇒ Object
The port to send queries to on the resolver
54 55 56 |
# File 'lib/Dnsruby/Resolver.rb', line 54 def port @port end |
#query_timeout ⇒ Object
Note that this timeout represents the total time a query may run for - multiple packets can be sent to multiple nameservers in this time. This is distinct from the SingleResolver per-packet timeout The query_timeout is not required - it will default to 0, which means “do not use query_timeout”. If this is the case then the timeout will be dictated by the retry_times and retry_delay attributes
96 97 98 |
# File 'lib/Dnsruby/Resolver.rb', line 96 def query_timeout @query_timeout end |
#recurse ⇒ Object
Should the Recursion Desired bit be set?
77 78 79 |
# File 'lib/Dnsruby/Resolver.rb', line 77 def recurse @recurse end |
#retry_delay ⇒ Object
The query will be tried across nameservers retry_times times, with a delay of retry_delay seconds between each retry. The first time round, retry_delay will be divided by the number of nameservers being targetted, and a new nameserver will be queried with the resultant delay.
101 102 103 |
# File 'lib/Dnsruby/Resolver.rb', line 101 def retry_delay @retry_delay end |
#retry_times ⇒ Object
The query will be tried across nameservers retry_times times, with a delay of retry_delay seconds between each retry. The first time round, retry_delay will be divided by the number of nameservers being targetted, and a new nameserver will be queried with the resultant delay.
101 102 103 |
# File 'lib/Dnsruby/Resolver.rb', line 101 def retry_times @retry_times end |
#single_resolvers ⇒ Object (readonly)
The array of SingleResolvers used for sending query messages
86 87 88 |
# File 'lib/Dnsruby/Resolver.rb', line 86 def single_resolvers @single_resolvers end |
#src_address ⇒ Object
The source address to send queries from
67 68 69 |
# File 'lib/Dnsruby/Resolver.rb', line 67 def src_address @src_address end |
#src_port ⇒ Object
The source port to send queries from
69 70 71 |
# File 'lib/Dnsruby/Resolver.rb', line 69 def src_port @src_port end |
#tsig_key ⇒ Object
Returns the value of attribute tsig_key.
60 61 62 |
# File 'lib/Dnsruby/Resolver.rb', line 60 def tsig_key @tsig_key end |
#udp_size ⇒ Object
The maximum UDP size to be used
80 81 82 |
# File 'lib/Dnsruby/Resolver.rb', line 80 def udp_size @udp_size end |
#use_tcp ⇒ Object
Should TCP be used as a transport rather than UDP?
57 58 59 |
# File 'lib/Dnsruby/Resolver.rb', line 57 def use_tcp @use_tcp end |
Instance Method Details
#add_config_nameservers ⇒ Object
499 500 501 502 503 504 |
# File 'lib/Dnsruby/Resolver.rb', line 499 def add_config_nameservers # Add the Config nameservers @config.nameserver.each do |ns| @single_resolvers.push(SingleResolver.new({:server=>ns})) end end |
#add_resolver(single) ⇒ Object
Add a new SingleResolver to the list of resolvers this Resolver object will query.
553 554 555 |
# File 'lib/Dnsruby/Resolver.rb', line 553 def add_resolver(single) @single_resolvers.push(single) end |
#close ⇒ Object
Close the Resolver. Unfinished queries are terminated with OtherResolError.
274 275 276 277 278 279 280 281 |
# File 'lib/Dnsruby/Resolver.rb', line 274 def close @mutex.synchronize { @query_list.each do |client_query_id, values| msg, client_queue, q, outstanding = values send_result_and_close(client_queue, client_query_id, q, nil, OtherResolvError.new("Resolver closing!")) end } end |
#generate_timeouts ⇒ Object
:nodoc: all
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 |
# File 'lib/Dnsruby/Resolver.rb', line 239 def generate_timeouts() #:nodoc: all # Create the timeouts for the query from the retry_times and retry_delay attributes. # These are created at the same time in case the parameters change during the life of the query. # # These should be absolute, rather than relative # The first value should be Time.now time_now = Time.now timeouts={} #These should be be pegged to the single_resolver they are targetting : # e.g. timeouts[timeout1]=nameserver retry_delay = @retry_delay @retry_times.times do |retry_count| if (retry_count>0) retry_delay *= 2 end servers=[] @single_resolvers.each do |r| servers.push(r.server) end @single_resolvers.each_index do |i| res= @single_resolvers[i] offset = (i*@retry_delay.to_f/@single_resolvers.length) if (retry_count==0) timeouts[time_now+offset]=[res, retry_count] else if (timeouts.has_key?(time_now+retry_delay+offset)) TheLog.error("Duplicate timeout key!") raise RuntimeError.new("Duplicate timeout key!") end timeouts[time_now+retry_delay+offset]=[res, retry_count] end end end return timeouts end |
#handle_error_response(select_queue, query_id, error, response) ⇒ Object
:nodoc: all
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 418 419 420 421 422 423 424 425 426 427 428 429 430 |
# File 'lib/Dnsruby/Resolver.rb', line 385 def handle_error_response(select_queue, query_id, error, response) #:nodoc: all #Handle an error @mutex.synchronize{ TheLog.debug("handling error #{error.class}, #{error}") # Check what sort of error it was : resolver, msg, client_query_id, retry_count = query_id msg, client_queue, select_queue, outstanding = @query_list[client_query_id] if (error.kind_of?(ResolvTimeout)) # - if it was a timeout, then check which number it was, and how many retries are expected on that server # - if it was the last retry, on the last server, then return a timeout to the client (and clean up) # - otherwise, continue # Do we have any more packets to send to this resolver? timeouts = @timeouts[client_query_id] if (outstanding.empty? && timeouts[1].values.empty?) TheLog.debug("Sending timeout to client") send_result_and_close(client_queue, client_query_id, select_queue, response, error) end elsif (error.kind_of?NXDomain) # - if it was an NXDomain, then return that to the client, and stop all new queries (and clean up) send_result_and_close(client_queue, client_query_id, select_queue, response, error) else # - if it was any other error, then remove that server from the list for that query # If a Too Many Open Files error, then don't remove, but let retry work. timeouts = @timeouts[client_query_id] if (!(error.to_s=~/Errno::EMFILE/)) TheLog.debug("Removing #{resolver.server} from resolver list for this query") timeouts[1].each do |key, value| res = value[0] if (res == resolver) timeouts[1].delete(key) end end else TheLog.debug("NOT Removing #{resolver.server} due to Errno::EMFILE") end # - if it was the last server, then return an error to the client (and clean up) if (outstanding.empty? && timeouts[1].values.empty?) # if (outstanding.empty?) TheLog.debug("Sending error to client") send_result_and_close(client_queue, client_query_id, select_queue, response, error) end end #@TODO@ If we're still sending packets for this query, but none are outstanding, then #jumpstart the next query? } end |
#handle_queue_event(queue, id) ⇒ Object
This method is called by the SelectThread (in the select thread) when the queue has a new item on it. The queue interface is used to separate producer/consumer threads, but we’re using it here in one thread. It’s probably a good idea to create a new “worker thread” to take items from the select thread queue and call this method in the worker thread.
Time to process a new queue event.
347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 |
# File 'lib/Dnsruby/Resolver.rb', line 347 def handle_queue_event(queue, id) #:nodoc: all # If we get a callback for an ID we don't know about, don't worry - # just ignore it. It may be for a query we've already completed. # # So, get the next response from the queue (presuming there is one!) if (queue.empty?) TheLog.fatal("Queue empty in handle_queue_event!") raise RuntimeError.new("Severe internal error - Queue empty in handle_queue_event") end event_id, response, error = queue.pop # We should remove this packet from the list of outstanding packets for this query resolver, msg, client_query_id, retry_count = id if (id != event_id) TheLog.error("Serious internal error!! #{id} expected, #{event_id} received") raise RuntimeError.new("Serious internal error!! #{id} expected, #{event_id} received") end @mutex.synchronize{ if (@query_list[client_query_id]==nil) TheLog.debug("Ignoring response for dead query") return end msg, client_queue, select_queue, outstanding = @query_list[client_query_id] if (!outstanding.include?id) TheLog.error("Query id not on outstanding list! #{outstanding.length} items. #{id} not on #{outstanding}") raise RuntimeError.new("Query id not on outstanding!") end outstanding.delete(id) } # if (event.kind_of?(Exception)) if (error != nil) handle_error_response(queue, event_id, error, response) else # if (event.kind_of?(Message)) handle_response(queue, event_id, response) # else # TheLog.error("Random object #{event.class} returned through queue to Resolver") end end |
#handle_response(select_queue, query_id, response) ⇒ Object
:nodoc: all
432 433 434 435 436 437 438 439 440 441 442 443 444 |
# File 'lib/Dnsruby/Resolver.rb', line 432 def handle_response(select_queue, query_id, response) #:nodoc: all # Handle a good response TheLog.debug("Handling good response") resolver, msg, client_query_id, retry_count = query_id @mutex.synchronize{ query, client_queue, s_queue, outstanding = @query_list[client_query_id] if (s_queue != select_queue) TheLog.error("Serious internal error : expected select queue #{s_queue}, got #{select_queue}") raise RuntimeError.new("Serious internal error : expected select queue #{s_queue}, got #{select_queue}") end send_result_and_close(client_queue, client_query_id, select_queue, response, nil) } end |
#nameserver=(n) ⇒ Object
557 558 559 560 561 |
# File 'lib/Dnsruby/Resolver.rb', line 557 def nameserver=(n) @single_resolvers=[] set_config_nameserver(n) add_config_nameservers end |
#query(name, type = Types.A, klass = Classes.IN) ⇒ Object
Query for a n. If a valid Message is received, then it is returned to the caller. Otherwise an exception (a Dnsruby::ResolvError or Dnsruby::ResolvTimeout) is raised.
require 'Dnsruby'
res = Dnsruby::Resolver.new
response = res.query("example.com") # defaults to Types.A, Classes.IN
response = res.query("example.com", Types.MX)
response = res.query("208.77.188.166") # IPv4 address so PTR query will be made
response = res.query("208.77.188.166", Types.PTR)
116 117 118 119 120 121 |
# File 'lib/Dnsruby/Resolver.rb', line 116 def query(name, type=Types.A, klass=Classes.IN) msg = Message.new msg.header.rd = 1 msg.add_question(name, type, klass) return (msg) end |
#reset_attributes ⇒ Object
:nodoc: all
514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 |
# File 'lib/Dnsruby/Resolver.rb', line 514 def reset_attributes #:nodoc: all # data structures @mutex=Mutex.new @query_list = {} # Attributes @timeouts = {} @query_timeout = DefaultQueryTimeout @retry_delay = DefaultRetryDelay @retry_times = DefaultRetryTimes @packet_timeout = DefaultPacketTimeout @port = DefaultPort @udp_size = DefaultUDPSize @use_tcp = false @tsig_key = nil @ignore_truncation = false @config = Config.new() @src_addr = '0.0.0.0' @src_port = 0 @recurse = true @persistent_udp = false @persistent_tcp = false @single_resolvers=[] end |
#send_async(msg, client_query_id, client_queue) ⇒ Object
Asynchronously sends a DNS packet (Dnsruby::Message). The client must pass in the Message to be sent, a client_query_id to identify the message and a client_queue (of class Queue) to pass the response back in.
A tuple of (query_id, response_message, exception) will be added to the client_queue.
example :
require 'Dnsruby'
res = Dnsruby::Resolver.new
query_id = 10 # can be any object you like
query_queue = Queue.new
res.send_async(Message.new("example.com", Types.MX), query_id, query_queue)
query_id += 1
res.send_async(Message.new("example.com", Types.A), query_id, query_queue)
# ...do a load of other stuff here...
2.times do
response_id, response, exception = query_queue.pop
# You can check the ID to see which query has been answered
if (exception == nil)
# deal with good response
else
# deal with problem
end
end
189 190 191 192 193 194 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 |
# File 'lib/Dnsruby/Resolver.rb', line 189 def send_async(msg, client_query_id, client_queue) # This is the whole point of the Resolver class. # We want to use multiple SingleResolvers to run a query. # So we kick off a system with select_thread where we send # a query with a queue, but log ourselves as observers for that # queue. When a new response is pushed on to the queue, then the # select thread will call this class' handler method IN THAT THREAD. # When the final response is known, this class then sticks it in # to the client queue. q = Queue.new if (!client_queue.kind_of?Queue) TheLog.error("Wrong type for client_queue in Resolver#send_async") client_queue.push([client_query_id, ArgumentError.new("Wrong type of client_queue passed to Dnsruby::Resolver#send_async - should have been Queue, was #{client_queue.class}")]) return end if (!msg.kind_of?Message) TheLog.error("Wrong type for msg in Resolver#send_async") client_queue.push([client_query_id, ArgumentError.new("Wrong type of msg passed to Dnsruby::Resolver#send_async - should have been Message, was #{msg.class}")]) return end tick_needed=false # add to our data structures @mutex.synchronize{ tick_needed = true if @query_list.empty? if (@query_list.has_key?client_query_id) TheLog.error("Duplicate query id requested (#{client_query_id}") client_queue.push([client_query_id, ArgumentError.new("Client query ID already in use")]) return end outstanding = [] @query_list[client_query_id]=[msg, client_queue, q, outstanding] query_timeout = Time.now+@query_timeout if (@query_timeout == 0) query_timeout = Time.now+31536000 # a year from now end @timeouts[client_query_id]=[query_timeout, generate_timeouts()] } # Now do querying stuff using SingleResolver # All this will be handled by the tick method (if we have 0 as the first timeout) st = SelectThread.instance st.add_observer(q, self) tick if tick_needed end |
#send_message(message) ⇒ Object
Send a message, and wait for the response. If a valid Message is received, then it is returned to the caller. Otherwise an exception (a Dnsruby::ResolvError or Dnsruby::ResolvTimeout) is raised.
send_async is called internally.
example :
require 'Dnsruby'
res = Dnsruby::Resolver.new
begin
response = res.(Message.new("example.com", Types.MX))
rescue ResolvError
# ...
rescue ResolvTimeout
# ...
end
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 |
# File 'lib/Dnsruby/Resolver.rb', line 139 def () TheLog.debug("Resolver : sending message") q = Queue.new send_async(, q, q) id, result, error = q.pop TheLog.debug("Resolver : result received") if (error != nil) raise error else return result end # case result # when Exception # # Pass them on # raise result # when Message # return result # else # TheLog.error("Unknown result returned : #{result}") # raise ResolvError.new("Unknown error, return : #{result}") # end end |
#send_result_and_close(client_queue, client_query_id, select_queue, msg, error) ⇒ Object
MUST BE CALLED IN A SYNCHRONIZED BLOCK!
Send the result back to the client, and close the socket for that query by removing the query from the select thread.
287 288 289 290 291 292 293 294 295 296 297 298 299 |
# File 'lib/Dnsruby/Resolver.rb', line 287 def send_result_and_close(client_queue, client_query_id, select_queue, msg, error) #:nodoc: all TheLog.debug("Sending result #{error} to client") # We might still get some callbacks, which we should ignore st = SelectThread.instance st.remove_observer(select_queue, self) # @mutex.synchronize{ # Remove the query from all of the data structures @timeouts.delete(client_query_id) @query_list.delete(client_query_id) # } # Return the response to the client client_queue.push([client_query_id, msg, error]) end |
#set_config_nameserver(n) ⇒ Object
506 507 508 509 510 511 512 |
# File 'lib/Dnsruby/Resolver.rb', line 506 def set_config_nameserver(n) if (n).kind_of?String @config.nameserver=[n] else @config.nameserver=n end end |
#tick ⇒ Object
This method is called ten times a second from the select loop, in the select thread. It should arguably be called from another worker thread… Each tick, we check if any timeouts have occurred. If so, we take the appropriate action : Return a timeout to the client, or send a new query
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 |
# File 'lib/Dnsruby/Resolver.rb', line 305 def tick #:nodoc: all # Handle the tick # Do we have any retries due to be sent yet? @mutex.synchronize{ time_now = Time.now @timeouts.keys.each do |client_query_id| msg, client_queue, select_queue, outstanding = @query_list[client_query_id] query_timeout, timeouts = @timeouts[client_query_id] if (query_timeout < Time.now) #Time the query out send_result_and_close(client_queue, client_query_id, select_queue, nil, ResolvTimeout.new("Query timed out")) next end timeouts_done = [] timeouts.keys.sort.each do |timeout| if (timeout < time_now) # Send the next query res, retry_count = timeouts[timeout] id = [res, msg, client_query_id, retry_count] TheLog.debug("Sending msg to #{res.server}") # We should keep a list of the queries which are outstanding outstanding.push(id) timeouts_done.push(timeout) timeouts.delete(timeout) res.send_async(msg, id, select_queue) else break end end timeouts_done.each do |t| timeouts.delete(t) end end } end |
#update ⇒ Object
:nodoc: all
539 540 541 542 543 544 545 546 547 548 549 |
# File 'lib/Dnsruby/Resolver.rb', line 539 def update #:nodoc: all #Update any resolvers we have with the latest config @single_resolvers.each do |res| [:port, :use_tcp, :tsig_key, :ignore_truncation, :packet_timeout, :src_address, :src_port, :persistent_tcp, :persistent_udp, :recurse, :udp_size].each do |param| res.send(param.to_s+"=", instance_variable_get("@"+param.to_s)) end end end |