Search code examples
rubynet-http

How to make an HTTP request without waiting for the response in Ruby


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)


Solution

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