API Ruby class
Ruby GEM
The API Ruby class(es) are now packaged together as a Ruby GEM. The latest GEM is available for download from the author's web site. The current version is 4.0.0 available here:
Or you can simply do:
gem install mtik
RDoc Documentation
The author's site also hosts Ruby RDoc documents for the classes implementing this API. The link is:
Examples
Several example scripts are included with the GEM or available for direct download from the author.
- tikcli - A command-line-like interactive ruby script. You can type MikroTik API commands and arguments directly and have them executed.
- tikcommand - A non-interactive command-line script to execute a single MikroTik API command and return the results to STDOUT.
- tikfetch - This non-interactive command-line script lets one instruct a MikroTik device to download one or more files from the provided URL(s).
- tikjson.rb - Another non-interactive command-line script that executes a single MikroTik API command and returns the results to STDOUT, however the results are encoded in JSON format. One could easily add CGI handling to this script, install it on a web server, and use it via a web browser. (The author in fact has done something like this for a JavaScript-based management web application that interacts with MikroTik devices via the API.)
Interactive examples using gem-supplied utility scripts
Here is are several example runs of the tikcli utility script included in the gem:
user@bsdhost:~$ tikcli 10.20.30.1 admin wrongpassword <<< '/login' (6) <<< END-OF-SENTENCE >>> '!done' (5) >>> 'ret=bf41fd4286417870c5eb86674a3b8fe4' (36) >>> '.tag=0' (6) >>> END-OF SENTENCE <<< '/login' (6) <<< '=name=admin' (11) <<< '=response=0003a042937d84ca4bc4cf7da50aadd507' (44) <<< END-OF-SENTENCE >>> '!trap' (5) >>> 'message=cannot log in' (21) >>> '.tag=1' (6) >>> END-OF SENTENCE >>> '!done' (5) >>> '.tag=1' (6) >>> END-OF SENTENCE === LOGIN ERROR: Login failed: cannot log in user@bsdhost:~$
That run was deliberately with the wrong password. Here's the login with the correct password:
user@bsdhost:~$ tikcli 10.20.30.1 admin correctpassword <<< '/login' (6) <<< END-OF-SENTENCE >>> '!done' (5) >>> 'ret=857e91c460620a02c3ca72ea7cf6c696' (36) >>> '.tag=0' (6) >>> END-OF SENTENCE <<< '/login' (6) <<< '=name=admin' (11) <<< '=response=001a77aec14077ec267c5297969ba1fa24' (44) <<< END-OF-SENTENCE >>> '!done' (5) >>> '.tag=1' (6) >>> END-OF SENTENCE Command (/quit to end):
At this point, the interactive client will accept MikroTik API commands in the format /command/name arg1 arg2 arg3
or also 12:/command/name arg1 arg2 arg3
where the 12: is a custom numeric prefix that tells the Ruby interactive client to auto-cancel the command in question after exactly 12 reply sentences are received, since otherwise a command with continuous output would hang the single-threaded interactive client in an endless reply-handling loop (until someone aborted it).
Arguments to API commands in this interactive client must ALREADY be in the API argument form. For example:
Command (/quit to end): /interface/getall ?name=ether1 === COMMAND: /interface/getall ?name=ether1 <<< '/interface/getall' (17) <<< '?name=ether1' (12) <<< END-OF-SENTENCE >>> '!re' (3) >>> '.id=*5' (6) >>> 'name=ether1' (11) >>> 'type=ether' (10) >>> 'mtu=1500' (8) >>> 'l2mtu=1500' (10) >>> 'bytes=26908361008/15001379552' (29) >>> 'packets=34880279/26382227' (25) >>> 'drops=0/0' (9) >>> 'errors=5/0' (10) >>> 'dynamic=false' (13) >>> 'running=true' (12) >>> 'disabled=false' (14) >>> 'comment=' (8) >>> '.tag=4' (6) >>> END-OF SENTENCE >>> '!done' (5) >>> '.tag=4' (6) >>> END-OF SENTENCE Command (/quit to end):
Did you see how the user properly prefixed the query parameter name
with the query character (question mark ?) and also paired it via = with the query value? With this example CLI, you must manually format all arguments as specified by the MikroTik API.
You may have noticed that this Ruby API implementation automatically adds a unique .tag
to every command. That means if you specify a tag value, the Ruby code will ignore it and use its own. It adds a tag so that replies can be correctly matched to the appropriate request.
Now here's the same query again, only add another parameter, =interval=1
so that the command will repeatedly send output each second. To avoid the command continuing forever, it will be prefixed with 6: (this CLI script strips the digit(s) and colon before sending the command to the device) to limit the number of response sentences to exactly six before the interactive client will automagically issue an appropriate /cancel =tag=XYZ
command to cancel it.
Command (/quit to end): 6:/interface/getall ?name=ether1 =interval=1 === COMMAND: /interface/getall ?name=ether1 =interval=1 <<< '/interface/getall' (17) <<< '?name=ether1' (12) <<< '=interval=1' (11) <<< END-OF-SENTENCE >>> '!re' (3) >>> '.id=*5' (6) >>> 'name=ether1' (11) >>> 'type=ether' (10) >>> 'mtu=1524' (8) >>> 'l2mtu=1524' (10) >>> 'bytes=26909135851/15002882324' (29) >>> 'packets=34886461/26387909' (25) >>> 'drops=0/0' (9) >>> 'errors=5/0' (10) >>> 'dynamic=false' (13) >>> 'running=true' (12) >>> 'disabled=false' (14) >>> 'comment=' (8) >>> '.tag=2' (6) >>> END-OF SENTENCE >>> '!re' (3) >>> '.id=*5' (6) >>> 'name=ether1' (11) >>> 'type=ether' (10) >>> 'mtu=1524' (8) >>> 'l2mtu=1524' (10) >>> 'bytes=26909140098/15002892177' (29) >>> 'packets=34886498/26387943' (25) >>> 'drops=0/0' (9) >>> 'errors=5/0' (10) >>> 'dynamic=false' (13) >>> 'running=true' (12) >>> 'disabled=false' (14) >>> 'comment=' (8) >>> '.tag=2' (6) >>> END-OF SENTENCE >>> '!re' (3) >>> '.id=*5' (6) >>> 'name=ether1' (11) >>> 'type=ether' (10) >>> 'mtu=1524' (8) >>> 'l2mtu=1524' (10) >>> 'bytes=26909141508/15002893670' (29) >>> 'packets=34886508/26387951' (25) >>> 'drops=0/0' (9) >>> 'errors=5/0' (10) >>> 'dynamic=false' (13) >>> 'running=true' (12) >>> 'disabled=false' (14) >>> 'comment=' (8) >>> '.tag=2' (6) >>> END-OF SENTENCE >>> '!re' (3) >>> '.id=*5' (6) >>> 'name=ether1' (11) >>> 'type=ether' (10) >>> 'mtu=1524' (8) >>> 'l2mtu=1524' (10) >>> 'bytes=26909143624/15002895110' (29) >>> 'packets=34886524/26387963' (25) >>> 'drops=0/0' (9) >>> 'errors=5/0' (10) >>> 'dynamic=false' (13) >>> 'running=true' (12) >>> 'disabled=false' (14) >>> 'comment=' (8) >>> '.tag=2' (6) >>> END-OF SENTENCE >>> '!re' (3) >>> '.id=*5' (6) >>> 'name=ether1' (11) >>> 'type=ether' (10) >>> 'mtu=1524' (8) >>> 'l2mtu=1524' (10) >>> 'bytes=26909144116/15002895406' (29) >>> 'packets=34886530/26387967' (25) >>> 'drops=0/0' (9) >>> 'errors=5/0' (10) >>> 'dynamic=false' (13) >>> 'running=true' (12) >>> 'disabled=false' (14) >>> 'comment=' (8) >>> '.tag=2' (6) >>> END-OF SENTENCE >>> '!re' (3) >>> '.id=*5' (6) >>> 'name=ether1' (11) >>> 'type=ether' (10) >>> 'mtu=1524' (8) >>> 'l2mtu=1524' (10) >>> 'bytes=26909144824/15002896659' (29) >>> 'packets=34886535/26387973' (25) >>> 'drops=0/0' (9) >>> 'errors=5/0' (10) >>> 'dynamic=false' (13) >>> 'running=true' (12) >>> 'disabled=false' (14) >>> 'comment=' (8) >>> '.tag=2' (6) >>> END-OF SENTENCE <<< '/cancel' (7) <<< '=tag=2' (6) <<< END-OF-SENTENCE >>> '!trap' (5) >>> 'category=2' (10) >>> 'message=interrupted' (19) >>> '.tag=2' (6) >>> END-OF SENTENCE === TRAP: 'interrupted' >>> '!done' (5) >>> '.tag=3' (6) >>> END-OF SENTENCE >>> '!done' (5) >>> '.tag=2' (6) >>> END-OF SENTENCE Command (/quit to end):
Using the Gem in Code
Execute an API command that returns endless replies until canceled
Suppose I wish to monitor traffic on an interface using the /interface/monitor-traffic
command for 10 seconds--or in other words, because devices usually send responses to this command once every second, I want to listen for and receive 10 replies, then cancel the command:
<nowiki> #!/usr/bin/env ruby require 'rubygems' require 'mtik' # Be verbose in output MTik::verbose = true # Connect to the device: connection = MTik::Connection.new :host => '10.0.0.1', :user => 'admin', :pass => 'password' # We are going to send a "monitor-traffic" command that will keep sending # output until we "cancel" the command. We only want to receive 10 responses: $reply_limit = 10 # Execute the command: $reply_count = 0 connection.get_reply_each( "/interface/monitor-traffic", "=interface=ether1", "=.proplist=rx-bits-per-second,tx-bits-per-second" ) do |request_object, reply_sentence| if reply_sentence.key?('!re') # We only pay attention to reply sentences # Print the reply sentence: p reply_sentence # Increment the reply counter: $reply_count += 1 # If we've reached our reply goal, cancel: if $reply_count >= $reply_limit # Cancel this command request: request_object.cancel end end end connection.close </noqiki>
Here's an example of output:
<<< '/login' (6) <<< '.tag=0' (6) <<< END-OF-SENTENCE >>> '!done' (5) >>> 'ret=cb21408d7123ebfc96bec24effe3409f' (36) >>> '.tag=0' (6) >>> END-OF SENTENCE <<< '/login' (6) <<< '=name=admin' (11) <<< '=response=0ce20d1ed4bd3ef821dc203a1ff2698461' (44) <<< '.tag=1' (6) <<< END-OF-SENTENCE >>> '!done' (5) >>> '.tag=1' (6) >>> END-OF SENTENCE <<< '/interface/monitor-traffic' (26) <<< '=interface=ether1' (17) <<< '=.proplist=rx-bits-per-second,tx-bits-per-second' (48) <<< '.tag=2' (6) <<< END-OF-SENTENCE >>> '!re' (3) >>> 'rx-bits-per-second=5744844' (26) >>> 'tx-bits-per-second=61787' (24) >>> '.tag=2' (6) >>> END-OF SENTENCE {"!re"=>nil, "rx-bits-per-second"=>"5744844", "tx-bits-per-second"=>"61787", ".tag"=>"2"} >>> '!re' (3) >>> 'rx-bits-per-second=4310298' (26) >>> 'tx-bits-per-second=44242' (24) >>> '.tag=2' (6) >>> END-OF SENTENCE {"!re"=>nil, "rx-bits-per-second"=>"4310298", "tx-bits-per-second"=>"44242", ".tag"=>"2"} >>> '!re' (3) >>> 'rx-bits-per-second=5442059' (26) >>> 'tx-bits-per-second=63398' (24) >>> '.tag=2' (6) >>> END-OF SENTENCE {"!re"=>nil, "rx-bits-per-second"=>"5442059", "tx-bits-per-second"=>"63398", ".tag"=>"2"} >>> '!re' (3) >>> 'rx-bits-per-second=5711572' (26) >>> 'tx-bits-per-second=63509' (24) >>> '.tag=2' (6) >>> END-OF SENTENCE {"!re"=>nil, "rx-bits-per-second"=>"5711572", "tx-bits-per-second"=>"63509", ".tag"=>"2"} >>> '!re' (3) >>> 'rx-bits-per-second=5711572' (26) >>> 'tx-bits-per-second=63509' (24) >>> '.tag=2' (6) >>> END-OF SENTENCE {"!re"=>nil, "rx-bits-per-second"=>"5711572", "tx-bits-per-second"=>"63509", ".tag"=>"2"} >>> '!re' (3) >>> 'rx-bits-per-second=3712135' (26) >>> 'tx-bits-per-second=28404' (24) >>> '.tag=2' (6) >>> END-OF SENTENCE {"!re"=>nil, "rx-bits-per-second"=>"3712135", "tx-bits-per-second"=>"28404", ".tag"=>"2"} >>> '!re' (3) >>> 'rx-bits-per-second=4822099' (26) >>> 'tx-bits-per-second=39160' (24) >>> '.tag=2' (6) >>> END-OF SENTENCE {"!re"=>nil, "rx-bits-per-second"=>"4822099", "tx-bits-per-second"=>"39160", ".tag"=>"2"} >>> '!re' (3) >>> 'rx-bits-per-second=4536317' (26) >>> 'tx-bits-per-second=52562' (24) >>> '.tag=2' (6) >>> END-OF SENTENCE {"!re"=>nil, "rx-bits-per-second"=>"4536317", "tx-bits-per-second"=>"52562", ".tag"=>"2"} >>> '!re' (3) >>> 'rx-bits-per-second=3976832' (26) >>> 'tx-bits-per-second=45331' (24) >>> '.tag=2' (6) >>> END-OF SENTENCE {"!re"=>nil, "rx-bits-per-second"=>"3976832", "tx-bits-per-second"=>"45331", ".tag"=>"2"} >>> '!re' (3) >>> 'rx-bits-per-second=4124776' (26) >>> 'tx-bits-per-second=80547' (24) >>> '.tag=2' (6) >>> END-OF SENTENCE {"!re"=>nil, "rx-bits-per-second"=>"4124776", "tx-bits-per-second"=>"80547", ".tag"=>"2"} <<< '/cancel' (7) <<< '=tag=2' (6) <<< '.tag=3' (6) <<< END-OF-SENTENCE >>> '!trap' (5) >>> 'category=2' (10) >>> 'message=interrupted' (19) >>> '.tag=2' (6) >>> END-OF SENTENCE >>> '!done' (5) >>> '.tag=3' (6) >>> END-OF SENTENCE >>> '!done' (5) >>> '.tag=2' (6) >>> END-OF SENTENCE
Updating DNS settings on multiple devices
Imagine I have a list of RouterOS devices that all need primary and secondary DNS settings updated. Here's an example Ruby script to do this:
#!/usr/bin/env ruby require 'rubygems' require 'mtik' ## List of devices (hostnames/IPs) to contact: devlist = [ '10.0.0.4', '10.0.0.5', '10.0.0.22', '10.1.44.22', '10.1.44.79' ] ## Example assumes all devices use the same API user/pass: USERNAME = 'admin' PASSWORD = 'password' ## Set DNS to these IPs (if these were the name servers in question): PRIMARYDNS = '10.20.30.2' SECONDARYDNS = '192.168.44.2' ## Set to 1 to do each device serially, or greater to fork parallel processes MAXFORK = 1 children = 0 devlist.each do |host| Kernel.fork do puts "#{host}: Connecting..." mt = nil begin mt = MTik::Connection.new( :host=>host, :user=>USERNAME, :pass=>PASSWORD ) rescue Errno::ETIMEDOUT, Errno::ENETUNREACH, Errno::EHOSTUNREACH => e puts "#{host}: Error connecting: #{e}" exit end ## The MTik::Connection#get_reply() method executes a command, then waits ## for it to complete (either with a '!done' or '!trap' response) before ## executing the callback code block. The call will block (execution of ## this script halts) and wait for the command to finish. Don't use this ## method if you need to handle simultaneous commands to a single device ## over a single API connection. Use an asynchronous calls send_request() ## and wait_for_reply(). mt.get_reply( '/ip/dns/set', "=primary-dns=#{PRIMARYDNS}", "=secondary-dns=#{SECONDARYDNS}" ) do |request, sentence| trap = request.reply.find_sentence('!trap') if trap.nil? puts "#{host}: Update command was sent." else puts "#{host}: An error occurred while setting DNS servers: #{trap['message']}" end end ## Now let's double-check the settings: mt.get_reply('/ip/dns/getall') do |request, sentence| trap = request.reply.find_sentence('!trap') if trap.nil? re = request.reply.find_sentence('!re') unless re.nil? ## Check DNS settings: if re['primary-dns'] == PRIMARYDNS && re['secondary-dns'] == SECONDARYDNS puts "#{host}: Successfully updated DNS servers." else puts "#{host}: WARNING: DNS servers DO NOT MATCH: primary-dns=" + "'#{re['primary-dns']}', secondary-dns='#{re['secondary-dns']}'" end else puts "#{host}: WARNING: '/ip/dns/getall' command did work to retrieve DNS settings!" end else puts "#{host}: An error occurred while setting DNS servers: #{trap['message']}" end end mt.close end children += 1 while children >= MAXFORK Process.wait children -= 1 end end while children > 1 Process.wait children -= 1 end
Output might look a bit like:
user@host:~/$ ./dnsupdate.rb 10.0.0.4: Connecting... 10.0.0.4: Update command was sent. 10.0.0.4: Successfully updated DNS servers. 10.0.0.5: Connecting... 10.0.0.5: Update command was sent. 10.0.0.5: Successfully updated DNS servers. ... MORE OUTPUT ... 10.1.44.79: Successfully updated DNS servers. user@host:~/$
The benefit of using Mikrotik's API is you can whip up a script to do something, then feed it a bunch of device IPs, login user IDs and passwords from a database, then execute desired commands on ALL of the devices. Check settings, change settings, monitor stats, etc.
Changing user group or deleting a user from a device
This shows how one can query a device for a list of configured users, then subsequently use API commands to remove or alter settings for users (if they exist) referencing them by API .id:
## Retrieve a list of all users on a RouterOS device (with associated IDs): users = {} mt.get_reply_each('/user/getall') do |r, s| if s.key?('!re') && s.key?('name') users[s['name']] = { :name => s['name'], :group => s['group'], :address => s['address'], :comment => s['comment'], :disabled => s['disabled'] == true, :id => s['.id'] } end end ## Remove a specific named user (if found on the device): mt.get_reply('/user/remove', "=.id=#{users['foo'][:id]}") if users.key?('foo') ## Make a named user (if found) an read-only user: mt.get_reply('/user/set', "=.id=#{users['foo'][:id]}", "=group=read")
One may wonder why not use ?name=foo
instead of using the ID parameter. The gem author has discovered that API commands that make changes often do not work even though the API does not respond with an error, unless the object to be changed is directly referenced by .id
. In updating or removing users, this appears to be the case.
Execute a multiple-response command and automatically cancel it to limit the number of replies
require 'rubygems' require 'mtik' # Be verbose in output MTik::verbose = true # Connect to the device: p connection = MTik::command( :host => '10.0.0.1', :user => 'username', :pass => 'password', :command => [ "/interface/monitor-traffic", "=interface=ether0", "=.proplist=rx-bits-per-second,tx-bits-per-second" ], :limit => 10 ## Auto-cancel after 10 replies )
In the above code, the /interface/monitor-traffic command
is executed using the blocking (non-event-style) MTik::command() library method. But because the monitor-traffic
command normally will keep sending replies (one per second) forever, the :limit => 10
parameter was passed. That makes the library count replies and automatically issue a /cancel
API command after the specified number of replies have been received. That way the blocking-style MTik::command()
method can be safely used without the program hanging forever.
Below is what this example might output. REMEMBER that MTik::verbose = true
so most of the output is due to that, and only the final line is the actual final data returned by the MTik::command()
call:
<<< '/login' (6) <<< '.tag=0' (6) <<< END-OF-SENTENCE >>> '!done' (5) >>> 'ret=2830ce30c78f9123d31544654d28a6e0' (36) >>> '.tag=0' (6) >>> END-OF SENTENCE <<< '/login' (6) <<< '=name=username' (9) <<< '=response=008a9eba141f9e0f8d2337ab84366298cb' (44) <<< '.tag=1' (6) <<< END-OF-SENTENCE >>> '!done' (5) >>> '.tag=1' (6) >>> END-OF SENTENCE <<< '/interface/monitor-traffic' (26) <<< '=interface=ether0' (21) <<< '=.proplist=rx-bits-per-second,tx-bits-per-second' (48) <<< '.tag=2' (6) <<< END-OF-SENTENCE >>> '!re' (3) >>> 'rx-bits-per-second=326944' (25) >>> 'tx-bits-per-second=1175812' (26) >>> '.tag=2' (6) >>> END-OF SENTENCE >>> '!re' (3) >>> 'rx-bits-per-second=84251' (24) >>> 'tx-bits-per-second=444610' (25) >>> '.tag=2' (6) >>> END-OF SENTENCE >>> '!re' (3) >>> 'rx-bits-per-second=111604' (25) >>> 'tx-bits-per-second=21782' (24) >>> '.tag=2' (6) >>> END-OF SENTENCE >>> '!re' (3) >>> 'rx-bits-per-second=116277' (25) >>> 'tx-bits-per-second=681' (22) >>> '.tag=2' (6) >>> END-OF SENTENCE >>> '!re' (3) >>> 'rx-bits-per-second=116277' (25) >>> 'tx-bits-per-second=681' (22) >>> '.tag=2' (6) >>> END-OF SENTENCE >>> '!re' (3) >>> 'rx-bits-per-second=339747' (25) >>> 'tx-bits-per-second=14495' (24) >>> '.tag=2' (6) >>> END-OF SENTENCE >>> '!re' (3) >>> 'rx-bits-per-second=106012' (25) >>> 'tx-bits-per-second=3952' (23) >>> '.tag=2' (6) >>> END-OF SENTENCE >>> '!re' (3) >>> 'rx-bits-per-second=118867' (25) >>> 'tx-bits-per-second=17370' (24) >>> '.tag=2' (6) >>> END-OF SENTENCE >>> '!re' (3) >>> 'rx-bits-per-second=130582' (25) >>> 'tx-bits-per-second=29941' (24) >>> '.tag=2' (6) >>> END-OF SENTENCE >>> '!re' (3) >>> 'rx-bits-per-second=130582' (25) >>> 'tx-bits-per-second=29941' (24) >>> '.tag=2' (6) >>> END-OF SENTENCE <<< '/cancel' (7) <<< '=tag=2' (6) <<< '.tag=3' (6) <<< END-OF-SENTENCE >>> '!trap' (5) >>> 'category=2' (10) >>> 'message=interrupted' (19) >>> '.tag=2' (6) >>> END-OF SENTENCE >>> '!done' (5) >>> '.tag=3' (6) >>> END-OF SENTENCE >>> '!done' (5) >>> '.tag=2' (6) >>> END-OF SENTENCE <<< '/quit' (5) <<< '.tag=4' (6) <<< END-OF-SENTENCE >>> '!fatal' (6) >>> 'session terminated on request' (29) >>> END-OF SENTENCE [[{"!re"=>nil, "rx-bits-per-second"=>"326944", "tx-bits-per-second"=>"1175812", ".tag"=>"2"}, {"!re"=>nil, "rx-bits-per-second"=>"84251", "tx-bits-per-second"=>"444610", ".tag"=>"2"}, {"!re"=>nil, "rx-bits-per-second"=>"111604", "tx-bits-per-second"=>"21782", ".tag"=>"2"}, {"!re"=>nil, "rx-bits-per-second"=>"116277", "tx-bits-per-second"=>"681", ".tag"=>"2"}, {"!re"=>nil, "rx-bits-per-second"=>"116277", "tx-bits-per-second"=>"681", ".tag"=>"2"}, {"!re"=>nil, "rx-bits-per-second"=>"339747", "tx-bits-per-second"=>"14495", ".tag"=>"2"}, {"!re"=>nil, "rx-bits-per-second"=>"106012", "tx-bits-per-second"=>"3952", ".tag"=>"2"}, {"!re"=>nil, "rx-bits-per-second"=>"118867", "tx-bits-per-second"=>"17370", ".tag"=>"2"}, {"!re"=>nil, "rx-bits-per-second"=>"130582", "tx-bits-per-second"=>"29941", ".tag"=>"2"}, {"!re"=>nil, "rx-bits-per-second"=>"130582", "tx-bits-per-second"=>"29941", ".tag"=>"2"}, {"!trap"=>nil, "category"=>"2", "message"=>"interrupted", ".tag"=>"2"}, {"!done"=>nil, ".tag"=>"2"}]]
Notes
- This has only been testing using Ruby 1.9.2 and Ruby 1.8.7 on several FreeBSD hosts, though it should work identically on other Ruby installations.
- Encoding/decoding longer words has NOT be thoroughly tested.
- Connection timeouts and auto-reconnections are NOT implemented.
- The above examples are single-threaded, but it is probably be safe to use within a multi-threaded Ruby application (untested).
- The gem uses an event driven callback style to send commands and receive responses. Multiple simultaneous commands may be executing over a single TCP API connection. If one fully utilizes the event-loop style of programming, one could have a single-threaded single process simultaneously executing many commands over many separate TCP API connections to different devices. To do so, one has to be careful to implement a main event loop to avoid blocking.
See the author's web site in the external links below for a link to the CHANGELOG.
See also
External links
- developers page: astounding hosted page
- forum discussion mikrotik forum and Other Mikrotik forum thread