yahns.git  about / heads / tags
sleepy, multi-threaded, non-blocking application server for Ruby
blob cd7d21049cb5aa9b333bae7b0aa4a796181b8fea 3305 bytes (raw)
$ git show maint:lib/yahns/openssl_client.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
 
# -*- encoding: binary -*-
# Copyright (C) 2013-2016 all contributors <yahns-public@yhbt.net>
# License: GPL-3.0+ (https://www.gnu.org/licenses/gpl-3.0.txt)
# frozen_string_literal: true

require_relative 'sendfile_compat'

# this is to be included into a Kgio::Socket-derived class
# this requires Ruby 2.1 and later for "exception: false"
module Yahns::OpenSSLClient # :nodoc:
  def self.included(cls)
    # Forward these methods to OpenSSL::SSL::SSLSocket so hijackers
    # can rely on stdlib methods instead of ugly kgio stuff that
    # we hope to phase out.
    # This is a bit weird, since OpenSSL::SSL::SSLSocket wraps
    # our actual socket, too, so we must take care to not blindly
    # use method_missing and cause infinite recursion
    %w(sync= read write readpartial write_nonblock read_nonblock
       print printf puts gets readlines readline getc
       readchar ungetc eof eof? << flush
       sysread syswrite).map!(&:to_sym).each do |m|
      cls.__send__(:define_method, m) { |*a| @ssl.__send__(m, *a) }
    end

    # block captures, ugh, but nobody really uses them
    %w(each each_line each_byte).map!(&:to_sym).each do |m|
      cls.__send__(:define_method, m) { |*a, &b| @ssl.__send__(m, *a, &b) }
    end
  end

  # this is special, called during IO initialization in Ruby
  def sync
    defined?(@ssl) ? @ssl.sync : super
  end

  def yahns_init_ssl(ssl_ctx)
    @need_accept = true
    @ssl = OpenSSL::SSL::SSLSocket.new(self, ssl_ctx)
    @ssl_blocked = nil
  end

  def kgio_trywrite(buf)
    buf = @ssl_blocked = buf.dup
    case rv = @ssl.write_nonblock(buf, exception: false)
    when :wait_readable, :wait_writable
      return rv # do not clear ssl_blocked
    when Integer
      rv = buf.bytesize == rv ? nil : buf.byteslice(rv, buf.bytesize - rv)
    end
    @ssl_blocked = nil
    rv
  end

  def kgio_trywritev(buf)
    kgio_trywrite(buf.join)
  end

  def kgio_syssend(buf, flags)
    kgio_trywrite(buf)
  end

  def kgio_tryread(len, buf)
    if @need_accept
      # most protocols require read before write, so we start the negotiation
      # process here:
      case rv = accept_nonblock(@ssl)
      when :wait_readable, :wait_writable, nil
        return rv
      end
      @need_accept = false
    end
    @ssl.read_nonblock(len, buf, exception: false)
  end

  def trysendfile(io, offset, count)
    return 0 if count == 0

    unless buf = @ssl_blocked
      count = 0x4000 if count > 0x4000
      buf = Thread.current[:yahns_sfbuf] ||= ''.dup
      io.pos = offset
      buf = io.read(count, buf) or return # nil for EOF
      buf = @ssl_blocked = buf.dup
    end

    # call write_nonblock directly since kgio_trywrite allocates
    # an unnecessary string
    case rv = @ssl.write_nonblock(buf, exception: false)
    when :wait_readable, :wait_writable
      return rv # do not clear ssl_blocked
    end
    @ssl_blocked = nil
    rv
  end

  def close
    @ssl.close # flushes SSLSocket
    super # IO#close
  end

  if RUBY_VERSION.to_f >= 2.3
    def accept_nonblock(ssl)
      ssl.accept_nonblock(exception: false)
    end
  else
    def accept_nonblock(ssl)
      ssl.accept_nonblock
    rescue IO::WaitReadable
      :wait_readable
    rescue IO::WaitWritable
      :wait_writable
    rescue OpenSSL::SSL::SSLError
      nil
    end
  end
end

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