Class: Hyperion::Server

Inherits:
Object
  • Object
show all
Defined in:
lib/hyperion/server.rb

Overview

Phase 2a server: bind a TCPServer, accept connections, schedule each on its own fiber via Async. Multiple in-flight requests run concurrently on a single OS thread. Keep-alive is still off — connection closes after one request (Phase 2b will add keep-alive).

Phase 7 (scoped): when ‘tls:` is supplied, wrap the listener in an OpenSSL::SSL::SSLServer with ALPN advertising `h2` + `http/1.1`. After the handshake, dispatch on the negotiated protocol — http/1.1 goes through Connection (real path); h2 goes to Http2Handler (505 stub until Phase 8).

Constant Summary collapse

DEFAULT_READ_TIMEOUT_SECONDS =
30
DEFAULT_THREAD_COUNT =
5

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(app:, host: '127.0.0.1', port: 9292, read_timeout: DEFAULT_READ_TIMEOUT_SECONDS, tls: nil, thread_count: DEFAULT_THREAD_COUNT) ⇒ Server

Returns a new instance of Server.



25
26
27
28
29
30
31
32
33
34
35
# File 'lib/hyperion/server.rb', line 25

def initialize(app:, host: '127.0.0.1', port: 9292, read_timeout: DEFAULT_READ_TIMEOUT_SECONDS,
               tls: nil, thread_count: DEFAULT_THREAD_COUNT)
  @host         = host
  @port         = port
  @app          = app
  @read_timeout = read_timeout
  @tls          = tls
  @thread_count = thread_count
  @thread_pool  = nil
  @stopped      = false
end

Instance Attribute Details

#hostObject (readonly)

Returns the value of attribute host.



23
24
25
# File 'lib/hyperion/server.rb', line 23

def host
  @host
end

#portObject (readonly)

Returns the value of attribute port.



23
24
25
# File 'lib/hyperion/server.rb', line 23

def port
  @port
end

Instance Method Details

#adopt_listener(sock) ⇒ Object

Phase 3: workers pass in a pre-bound, SO_REUSEPORT-set socket built by Hyperion::Worker. Bypasses #listen but keeps the rest of the accept loop intact since Socket and TCPServer both quack #accept_nonblock.

Phase 8: when ‘tls:` was supplied to the constructor, also build the SSL context here so the accept loop can wrap incoming connections. Each worker builds its own context — they don’t share state.



61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/hyperion/server.rb', line 61

def adopt_listener(sock)
  @server = sock
  @tcp_server = sock
  @port = case sock
          when ::TCPServer
            sock.addr[1]
          else
            sock.local_address.ip_port
          end
  @ssl_ctx = TLS.context(cert: @tls[:cert], key: @tls[:key], chain: @tls[:chain]) if @tls
  self
end

#listenObject



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/hyperion/server.rb', line 37

def listen
  tcp = ::TCPServer.new(@host, @port)
  @port = tcp.addr[1]

  if @tls
    @ssl_ctx = TLS.context(cert: @tls[:cert], key: @tls[:key], chain: @tls[:chain])
    ssl_server = ::OpenSSL::SSL::SSLServer.new(tcp, @ssl_ctx)
    ssl_server.start_immediately = false
    @server = ssl_server
    @tcp_server = tcp
  else
    @server = tcp
    @tcp_server = tcp
  end
  self
end

#run_oneObject



74
75
76
77
78
79
80
81
82
# File 'lib/hyperion/server.rb', line 74

def run_one
  Async do
    socket = blocking_accept
    next unless socket

    apply_timeout(socket)
    dispatch(socket)
  end.wait
end

#startObject



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
# File 'lib/hyperion/server.rb', line 84

def start
  listen unless @server
  @thread_pool = ThreadPool.new(size: @thread_count) if @thread_count.positive?

  Async do |task|
    until @stopped
      socket = accept_or_nil
      next unless socket

      apply_timeout(socket)
      # Plain HTTP/1.1 with a pool: submit straight to the worker — no
      # fiber wrap needed (submit_connection returns immediately and the
      # worker thread owns the connection for its lifetime).
      # TLS still goes through a fiber: ALPN negotiation determines h2
      # vs http/1.1, and h2 needs the fiber because each stream is its
      # own fiber inside Http2Handler.
      if @thread_pool && !@tls
        @thread_pool.submit_connection(socket, @app)
      else
        task.async { dispatch(socket) }
      end
    end
  end
ensure
  @thread_pool&.shutdown
end

#stopObject



111
112
113
114
115
116
# File 'lib/hyperion/server.rb', line 111

def stop
  @stopped = true
  @server&.close
  @server = nil
  @tcp_server = nil
end