yahns.git  about / heads / tags
sleepy, multi-threaded, non-blocking application server for Ruby
blob 62a6da2cb11aa50dcf73f9ab7984d9e345a80e07 3986 bytes (raw)
$ git show v0.0.1:lib/yahns/socket_helper.rb	# shows this blob on the CLI

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
 
# -*- encoding: binary -*-
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
# this is only meant for Yahns::Server
module Yahns::SocketHelper # :nodoc:
  def set_server_sockopt(sock, opt)
    opt = {backlog: 1024}.merge!(opt) if opt
    sock.close_on_exec = true

    TCPSocket === sock and sock.setsockopt(:IPPROTO_TCP, :TCP_NODELAY, 1)
    sock.setsockopt(:SOL_SOCKET, :SO_KEEPALIVE, 1)

    if opt[:rcvbuf] || opt[:sndbuf]
      log_buffer_sizes(sock, "before: ")
      { SO_RCVBUF: :rcvbuf, SO_SNDBUF: :sndbuf }.each do |optname,cfgname|
        val = opt[cfgname] and sock.setsockopt(:SOL_SOCKET, optname, val)
      end
      log_buffer_sizes(sock, " after: ")
    end
    sock.listen(opt[:backlog])
  rescue => e
    Yahns::Log.exception(@logger, "#{sock_name(sock)} #{opt.inspect}", e)
  end

  def log_buffer_sizes(sock, pfx = '')
    rcvbuf = sock.getsockopt(:SOL_SOCKET, :SO_RCVBUF).int
    sndbuf = sock.getsockopt(:SOL_SOCKET, :SO_SNDBUF).int
    @logger.info("#{pfx}#{sock_name(sock)} rcvbuf=#{rcvbuf} sndbuf=#{sndbuf}")
  end

  # creates a new server, socket. address may be a HOST:PORT or
  # an absolute path to a UNIX socket.  address can even be a Socket
  # object in which case it is immediately returned
  def bind_listen(address, opt)
    return address unless String === address
    opt ||= {}

    sock = if address[0] == ?/
      if File.exist?(address)
        if File.socket?(address)
          begin
            UNIXSocket.new(address).close
            # fall through, try to bind(2) and fail with EADDRINUSE
            # (or succeed from a small race condition we can't sanely avoid).
          rescue Errno::ECONNREFUSED
            @logger.info "unlinking existing socket=#{address}"
            File.unlink(address)
          end
        else
          raise ArgumentError,
                "socket=#{address} specified but it is not a socket!"
        end
      end
      old_umask = File.umask(opt[:umask] || 0)
      begin
        Kgio::UNIXServer.new(address)
      ensure
        File.umask(old_umask)
      end
    elsif /\A\[([a-fA-F0-9:]+)\]:(\d+)\z/ =~ address
      new_ipv6_server($1, $2.to_i, opt)
    elsif /\A(\d+\.\d+\.\d+\.\d+):(\d+)\z/ =~ address
      Kgio::TCPServer.new($1, $2.to_i)
    else
      raise ArgumentError, "Don't know how to bind: #{address}"
    end
    set_server_sockopt(sock, opt)
    sock
  end

  def new_ipv6_server(addr, port, opt)
    opt.key?(:ipv6only) or return Kgio::TCPServer.new(addr, port)
    sock = Socket.new(:AF_INET6, :SOCK_STREAM, 0)
    sock.setsockopt(:IPPROTO_IPV6, :IPV6_V6ONLY, opt[:ipv6only] ? 1 : 0)
    sock.setsockopt(:SOL_SOCKET, :SO_REUSEADDR, 1)
    sock.bind(Socket.pack_sockaddr_in(port, addr))
    sock.autoclose = false
    Kgio::TCPServer.for_fd(sock.fileno)
  end

  # returns rfc2732-style (e.g. "[::1]:666") addresses for IPv6
  def tcp_name(sock)
    port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
    /:/ =~ addr ? "[#{addr}]:#{port}" : "#{addr}:#{port}"
  end

  # Returns the configuration name of a socket as a string.  sock may
  # be a string value, in which case it is returned as-is
  # Warning: TCP sockets may not always return the name given to it.
  def sock_name(sock)
    case sock
    when String then sock
    when UNIXServer
      Socket.unpack_sockaddr_un(sock.getsockname)
    when TCPServer
      tcp_name(sock)
    when Socket
      begin
        tcp_name(sock)
      rescue ArgumentError
        Socket.unpack_sockaddr_un(sock.getsockname)
      end
    else
      raise ArgumentError, "Unhandled class #{sock.class}: #{sock.inspect}"
    end
  end

  # casts a given Socket to be a TCPServer or UNIXServer
  def server_cast(sock)
    sock.autoclose = false
    begin
      Socket.unpack_sockaddr_in(sock.getsockname)
      Kgio::TCPServer.for_fd(sock.fileno)
    rescue ArgumentError
      Kgio::UNIXServer.for_fd(sock.fileno)
    end
  end
end

git clone git://yhbt.net/yahns.git
git clone https://yhbt.net/yahns.git