I just want to hit a server from inside of my Rails controller, but not wait for the response. Is this possible? (without launching other threads I can't do that for performance reasons)
It's possible by opening a socket and closing it. This will establish a connection and close the connection without transferring any data within the connection's context...
...you will need to wait for the connection to open - although there might be a way to avoid that.
require 'socket'
# opens a connection, will wait for connection but not for data.
s = TCPSocket.new 'host.url.com', 80
# closes the connection
s.close
It's probably the equivalent of a ping and will not open a new thread... although, it's not totally asynchronous.
With an HTTP request, the code might look something like this:
require 'socket'
host = 'www.google.com'
# opens a connection, will wait for connection but not for data.
s = TCPSocket.new host, 80
# send a GET request for '/' .
s.puts "GET / HTTP/1.1\r\nHost: #{host}\r\n\r\n"
# closes the connection
s.close
You can search for more info about HTTP requests on stack exchange and get some ideas, like here.
Just to clarify (due to comments):
This WILL introduce the latency related to establishing the connection (and sending the request), but you WILL NOT have to wait for the reply to be processed and received.
Dropping the connection (closing your half of the socket) could have any of the following effects - all of which are assuming a decent web server:
if s.close
is completed BEFORE the response is fully sent by the web server, the web server will first process the request and then an exception will be raised on the web-server's socket when it tries to send the data. The web server should then close the socket and release any resources.
if s.close
is completed AFTER the response is fully sent by the web server, then the server might either: 1. close the socket immediately (normal HTTP 1 behavior) OR 2. keep the connection alive until a timeout occurs (optional HTTP 1.1 behavior) - Timeout is usually about 10 seconds.
Hitting the web server repeatedly in very small intervals might cause a DOS security flag to be raised and future connections to be blocked (this is true regardless of how you hit the web server).
I would probably opt to use a worker thread, like so:
I do believe that running a separate thread might not be as expensive as you believe. It's possible to have one thread cycle for all the asynchronous web requests.
here's a thought:
require 'socket'
REQUESTS_MUTEX = Mutex.new
REQUESTS_QUE = []
REQUESTS_THREAD = Thread.new do
begin
loop do
sleep 0.5 while REQUESTS_QUE.empty?
host, path = REQUESTS_MUTEX.synchronize {REQUESTS_QUE.shift}
# the following will open a connection and start a request,
# but it's easier to use the built in HTTP API...
# although it will wait for a response.
s = TCPSocket.new host, 80
s.puts "GET #{path} HTTP/1.1\r\nHost: #{host}\r\n\r\n"
s.close
# log here:
puts "requested #{path} from #{host}."
end
rescue Exception => e
retry
end
end
def asynch_request host, path = '/'
REQUESTS_MUTEX.synchronize {REQUESTS_QUE << [host, path]}
REQUESTS_THREAD.alive?
end
Now, for every request, you can simply call asynch_request
and the cycling thread should hit the web server as soon as it wakes up and notices the que.
You can test it from the terminal by pasting a few requests in a bunch:
asynch_request 'www.google.com'
asynch_request 'www.yahoo.com'
asynch_request 'I.Dont.exist.com'
asynch_request 'very bad host address...'
asynch_request 'www.github.com'
Notice the silent fails (you can tweak the code).