Libuv FFI bindings for Ruby
Libuv is a cross platform asynchronous IO implementation that powers NodeJS. It supports sockets, both UDP and TCP, filesystem watch, TTY, Pipes and other asynchronous primitives like timer, check, prepare and idle.
The Libuv gem contains Libuv and a Ruby wrapper that implements pipelined promises for asynchronous flow control and coroutines / futures for untangling evented code
Usage
Libuv supports multiple reactors that can run on different threads.
For convenience the thread local or default reactor can be accessed via the reactor
method
You can pass a block to be executed on the reactor and the reactor will run until there is nothing left to do.
require 'libuv'
reactor do |reactor|
reactor.timer {
puts "5 seconds passed"
}.start(5000)
end
puts "reactor stopped. No more IO to process"
Promises are used to simplify code flow.
require 'libuv'
reactor do |reactor|
reactor.tcp { |data, socket|
puts "received: #{data}"
socket.close
}
.connect('127.0.0.1', 3000) { |socket|
socket.start_read
.write("GET / HTTP/1.1\r\n\r\n")
}
.catch { |error|
puts "error: #{error}"
}
.finally {
puts "socket closed"
}
end
Continuations are used if callbacks are not defined
require 'libuv'
reactor do |reactor|
begin
reactor.tcp { |data, socket|
puts "received: #{data}"
socket.close
}
.connect('127.0.0.1', 3000)
.start_read
.write("GET / HTTP/1.1\r\n\r\n")
rescue => error
puts "error: #{error}"
end
end
Any promise can be converted into a continuation
require 'libuv'
reactor do |reactor|
# Perform work on the thread pool with promises
reactor.work {
10 * 2
}.then { |result|
puts "result using a promise #{result}"
}
# Use the coroutine helper to obtain the result without a callback
result = reactor.work {
10 * 3
}.value
puts "no additional callbacks here #{result}"
end
Check out the yard documentation
Installation
gem install libuv
or
git clone https://github.com/cotag/libuv.git
cd libuv
bundle install
rake compile
Prerequisites
- The installation also requires python 2.x to be installed and available on the PATH
- setting the environmental variable
USE_GLOBAL_LIBUV
will prevent compiling the packaged version.- if you have a compatible
libuv.(so | dylib | dll)
on the PATH already
- if you have a compatible
Windows users will additionally require:
- A copy of Visual Studio 2010 or later. Visual Studio Express works fine.
- A copy of OpenSSL matching the installed ruby (x86 / x64)
- If using jRuby then GCC is also required
- Setup the paths as described on the gcc page
- Add required environmental variable
set LIBRARY_PATH=X:\win-builds-64\lib;X:\win-builds-64\x86_64-w64-mingw32\lib
Features
- TCP (with TLS support)
- UDP
- TTY
- Pipes
- Timer
- Prepare
- Check
- Idle
- Signals
- Async callbacks
- Async DNS Resolution
- Filesystem Events
- Filesystem manipulation
- File manipulation
- Errors (with a catch-all fallback for anything unhandled on the event reactor)
- Work queue (thread pool)
- Coroutines / futures (makes use of Fibers)
Server Name Indication
You can host a TLS enabled server with multiple hostnames using SNI.
server = reactor.tcp
server.bind('0.0.0.0', 3000, **{
hosts: [{
private_key: '/blah.key',
cert_chain: '/blah.crt',
host_name: 'somehost.com',
},
{
private_key: '/blah2.key',
cert_chain: '/blah2.crt',
host_name: 'somehost2.com'
},
{
private_key: '/blah3.key',
cert_chain: '/blah3.crt',
host_name: 'somehost3.com'
}]
}) do |client|
client.start_tls
client.start_read
end
# at some point later
server.add_host(private_key: '/blah4.key', cert_chain: '/blah4.crt', host_name: 'somehost4.com')
server.remove_host('somehost2.com')
You don't have to specify any hosts at binding time.