2

I've found a lot of general information about running a server using unix sockets, but I can't quite get it working. Here's all I want to do. The server creates a unix socket, then listens on it. When a request comes in, it processes the request, then sends out a response. For now it doesn't matter when the request is or what processing happens. "Hello world" is fine for now.

Here are the server and client scripts I have right now. As far as I can tell, neither works.

The server:

#!/usr/bin/ruby -w
require 'socket'

server = UNIXServer.new('/tmp/socket-simple')
socket = server.accept
socket.write 'Hello world'
socket.close
server.close

The client:

#!/usr/bin/ruby -w
require 'net_http_unix'

request = Net::HTTP::Get.new('whatever')
client = NetX::HTTPUnix.new('unix://' + '/tmp/socket-simple')
response = client.request(request)
puts response.body

What I expect to happen is that the client sends that simple request, gets back "Hello world", and displays that response. Here's what actually happens.

The client script outputs this error:

Traceback (most recent call last):
    16: from ./socket-client.rb:8:in `<main>'
    15: from /usr/lib/ruby/2.5.0/net/http.rb:1458:in `request'
    14: from /usr/lib/ruby/2.5.0/net/http.rb:910:in `start'
    13: from /usr/lib/ruby/2.5.0/net/http.rb:1460:in `block in request'
    12: from /usr/lib/ruby/2.5.0/net/http.rb:1467:in `request'
    11: from /usr/lib/ruby/2.5.0/net/http.rb:1493:in `transport_request'
    10: from /usr/lib/ruby/2.5.0/net/http.rb:1536:in `begin_transport'
    9: from /var/lib/gems/2.5.0/gems/net_http_unix-0.2.2/lib/net_x/http_unix.rb:24:in `connect'
    8: from /var/lib/gems/2.5.0/gems/net_http_unix-0.2.2/lib/net_x/http_unix.rb:35:in `connect_unix'
    7: from /usr/lib/ruby/2.5.0/timeout.rb:108:in `timeout'
    6: from /usr/lib/ruby/2.5.0/timeout.rb:33:in `catch'
    5: from /usr/lib/ruby/2.5.0/timeout.rb:33:in `catch'
    4: from /usr/lib/ruby/2.5.0/timeout.rb:33:in `block in catch'
    3: from /usr/lib/ruby/2.5.0/timeout.rb:93:in `block in timeout'
    2: from /var/lib/gems/2.5.0/gems/net_http_unix-0.2.2/lib/net_x/http_unix.rb:35:in `block in connect_unix'
    1: from /var/lib/gems/2.5.0/gems/net_http_unix-0.2.2/lib/net_x/http_unix.rb:35:in `open'
/var/lib/gems/2.5.0/gems/net_http_unix-0.2.2/lib/net_x/http_unix.rb:35:in `initialize': Connection refused - connect(2) for /tmp/socket-simple (Errno::ECONNREFUSED)

The server seems to work OK, except that it leaves the unix socket file open. I can kill that socket by deleting /tmp/socket-simple.

So I'm pretty lost here. Can anybody provide I working examples of client and server scripts that do what I want?

2 Answers 2

3

As far as I can tell, neither works.

This is incorrect; your client is fine. Your server, however, is not.

Your client code is trying to communicate in HTTP. Nothing in your server code is implementing the HTTP semantics.

I believe what is happening to you is this: Client connects. Server says "Hello world" and closes connection, just as Client starts sending the header. Client figures out it is talking to a wall, and since transmitting the header is mandatory, it gives up with an error.

This means, you have to at least let the client say the HTTP header before you close connection. You also have to send the HTTP response header, or the HTTP client will find itself extremely confused.

Here's the minimal server code that works for me:

#!/usr/bin/ruby -w
require 'socket'

begin
  server = UNIXServer.new('/tmp/socket-simple')
  socket = server.accept

  # read the headers, at least
  while socket.gets.chomp != ""
  end

  # send the HTTP response header
  socket.puts "HTTP/1.1 200 OK"
  socket.puts "Content-Type: text/plain"
  socket.puts

  # only now can you send a response body
  socket.puts 'Hello world'
  socket.close
  server.close
ensure
  File.unlink('/tmp/socket-simple')
end

This is super-simplistic though, and not very correct (it would fail at POST, for example, and many other details). You're probably better off using Rack, which already knows how to talk HTTP properly. Rack can use a UNIX socket if you supply the path in its Host argument. For example:

require 'rack'
require 'thin'

class TestApp
  def call(env)
    [200, {"Content-Type" => "text/plain"}, ["Hello World!"]]
  end
end

app = TestApp.new
Rack::Handler.get('thin').run(app, Host: '/tmp/socket-simple')

EDIT: Here's the no-dependency version using WEBrick. WEBrick does not support UNIX sockets directly, so we have to prevent it from trying to listen, and insert our own listener:

require 'webrick'
require 'socket'

begin
  UNIXServer.open('/tmp/socket-simple') do |ssocket|
    server = WEBrick::HTTPServer.new(DoNotListen: true)
    server.listeners << ssocket

    server.mount_proc '/' do |req, res|
      res.set_content_type('text/plain')
      res.body = "Hello, world!"
    end

    server.start
  end
ensure
  File.unlink('/tmp/socket-simple')
end
Sign up to request clarification or add additional context in comments.

2 Comments

Is there a way to do this using WEBrick? I'd like to at least look into doing this with what ships with Ruby, though I'm ok using Thin if WEBrick isn't the right tool for the job.
Perhaps I should explain my requirements. I'm not developing a production-ready app. I'm just developing a gem, and I want to include a small, lightweight server that people can run just to get an idea how it works. It will be clear in the documentation that it's just for experimental purposes. For that reason, I'd rather only use what ships with Ruby.
3

I am unsure what net_http_unix is and why would you bring 3rd-party solutions when everything to accomplish a task is there in core.

Socket ruby documentation. Here is a copy-paste from there.

# echo server
Socket.unix_server_loop("/tmp/sock") do |sock, _client|
  begin      
    IO.copy_stream(sock, sock)          
  ensure          
    sock.close          
  end          
end

# client (another terminal)
Socket.unix("/tmp/sock") do |sock|
  t = Thread.new { IO.copy_stream(sock, STDOUT) }  
  IO.copy_stream(STDIN, sock)  
  t.join  
end

In the second terminal you might type anything and it will be echoed. Also you might put debug output to server block to track the interchange.

1 Comment

net_http_unix "is a small wrapper around Ruby's Net::HTTP interface that provides support for unix domain sockets." OP's problem is not handling HTTP semantics, which your answer does not address (and the problem is on serverside, which does not use net_http_unix)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.