module Tipi

Constants

ALPN_PROTOCOLS
CERTIFICATE_STORE_DEFAULT_DB_PATH
CERTIFICATE_STORE_DEFAULT_DIR
H2_PROTOCOL
VERSION

Public Class Methods

accept_loop(server, opts, &handler) click to toggle source
# File lib/tipi.rb, line 39
def accept_loop(server, opts, &handler)
  server.accept_loop do |client|
    spin { client_loop(client, opts, &handler) }
  rescue OpenSSL::SSL::SSLError
    # disregard
  end
end
client_loop(client, opts, &handler) click to toggle source
# File lib/tipi.rb, line 47
def client_loop(client, opts, &handler)
  client.no_delay if client.respond_to?(:no_delay)
  adapter = protocol_adapter(client, opts)
  adapter.each(&handler)
ensure
  client.close rescue nil
end
default_certificate_store() click to toggle source
# File lib/tipi.rb, line 71
def default_certificate_store
  FileUtils.mkdir(CERTIFICATE_STORE_DEFAULT_DIR) rescue nil
  Tipi::ACME::SQLiteCertificateStore.new(CERTIFICATE_STORE_DEFAULT_DB_PATH)
end
full_service( http_port: 10080, https_port: 10443, certificate_store: default_certificate_store, app: nil, &block ) click to toggle source
# File lib/tipi.rb, line 76
def full_service(
  http_port: 10080,
  https_port: 10443,
  certificate_store: default_certificate_store,
  app: nil, &block
)
  app ||= block
  raise "No app given" unless app

  http_handler = ->(r) { r.redirect("https://#{r.host}#{r.path}") }

  ctx = OpenSSL::SSL::SSLContext.new
  # ctx.ciphers = 'ECDH+aRSA'
  Polyphony::Net.setup_alpn(ctx, Tipi::ALPN_PROTOCOLS)

  challenge_handler = Tipi::ACME::HTTPChallengeHandler.new
  certificate_manager = Tipi::ACME::CertificateManager.new(
    master_ctx: ctx,
    store: certificate_store,
    challenge_handler: challenge_handler
  )

  http_listener = spin do
    opts = {
      reuse_addr:   true,
      reuse_port:   true,
      dont_linger:  true,
    }
    puts "Listening for HTTP on localhost:#{http_port}"
    server = Polyphony::Net.tcp_listen('0.0.0.0', http_port, opts)
    wrapped_handler = certificate_manager.challenge_routing_app(http_handler)
    server.accept_loop do |client|
      spin do
        Tipi.client_loop(client, opts, &wrapped_handler)
      rescue => e
        puts "Uncaught error in HTTP listener: #{e.inspect}"
      end
    end
  ensure
    server.close
  end

  https_listener = spin do
    opts = {
      reuse_addr:     true,
      reuse_port:     true,
      dont_linger:    true,
      secure_context: ctx,
    }
  
    puts "Listening for HTTPS on localhost:#{https_port}"
    server = Polyphony::Net.tcp_listen('0.0.0.0', https_port, opts)
    loop do
      client = server.accept
      spin do
        Tipi.client_loop(client, opts, &app)
      rescue => e
        puts "Uncaught error in HTTPS listener: #{e.inspect}"
      end
    rescue OpenSSL::SSL::SSLError, SystemCallError, TypeError
      # ignore
    end
  ensure
    server.close
  end

  Fiber.await(http_listener, https_listener)
end
listen(host, port, opts = {}) click to toggle source
# File lib/tipi.rb, line 30
def listen(host, port, opts = {})
  opts[:alpn_protocols] = ALPN_PROTOCOLS
  Polyphony::Net.tcp_listen(host, port, opts).tap do |socket|
    socket.define_singleton_method(:each) do |&block|
      ::Tipi.accept_loop(socket, opts, &block)
    end
  end
end
protocol_adapter(socket, opts) click to toggle source
# File lib/tipi.rb, line 55
def protocol_adapter(socket, opts)
  use_http2 = socket.respond_to?(:alpn_protocol) &&
              socket.alpn_protocol == H2_PROTOCOL
  klass = use_http2 ? HTTP2Adapter : HTTP1Adapter
  klass.new(socket, opts)
end
route(&block) click to toggle source
# File lib/tipi.rb, line 62
def route(&block)
  proc { |req| req.route(&block) }
end
serve(host, port, opts = {}, &handler) click to toggle source
# File lib/tipi.rb, line 22
def serve(host, port, opts = {}, &handler)
  opts[:alpn_protocols] = ALPN_PROTOCOLS
  server = Polyphony::Net.tcp_listen(host, port, opts)
  accept_loop(server, opts, &handler)
ensure
  server&.close
end