Class: Tep::App

Inherits:
Object
  • Object
show all
Defined in:
lib/tep/app.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeApp

Returns a new instance of App.



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
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/tep/app.rb', line 74

def initialize
  @router         = Router.new
  @static_root    = ""
  @session_secret = ""
  @tls_cert       = ""   # inbound TLS cert path (tep#148 ph2; "" = plain HTTP)
  @tls_key        = ""   # inbound TLS key path
  @before_filter  = Filter.new   # no-op default
  @after_filter   = Filter.new
  @auth_filter    = Filter.new   # no-op until Tep::Auth.install!
  @auth_bearer_secret = ""
  # Type-seed the OAuth2 registries with a single dummy entry +
  # immediate drop so the PtrArray slot type is pinned.
  @auth_oauth2_clients = [Tep::AuthOAuth2Client.new("_", "", "", [:_])]
  @auth_oauth2_clients.delete_at(0)
  @auth_oauth2_codes = [Tep::AuthOAuth2Code.new("_", "", "", "", 0)]
  @auth_oauth2_codes.delete_at(0)
  # Same type-seed pattern for the Broadcast subscriber registry.
  @broadcast_subs = [Tep::BroadcastSubscription.new("_", -1, 0)]
  @broadcast_subs.delete_at(0)
  # And for the Presence entry registry.
  @presence_entries = [Tep::PresenceEntry.new("_", "", :human, "", -1, 0)]
  @presence_entries.delete_at(0)
  @presence_pg_enabled   = 0
  @presence_pg_worker_id = ""
  @broadcast_pg_enabled = 0
  @broadcast_pg_channel = ""
  # Seed broadcast_pg_conn later via lib/tep.rb's setter seed
  # (APP.set_broadcast_pg_conn(PG::Connection.new(""))) -- module
  # load order means PG::Connection isn't safely callable from
  # App#initialize when this is loaded before pg.rb's full surface.
  @nf_handler     = Handler.new
  @asset_bodies   = Tep.str_hash # path -> bytes (filled at boot
  @asset_mimes    = Tep.str_hash # by Tep::Assets._add lines
                                 # the bin/tep translator emits)
  @asset_etags    = Tep.str_hash # path -> content-hash ETag (#152)
  # FiberSlot array for the cooperative scheduler. Initialise
  # with a noop-bodied slot to pin the array element type, then
  # drop it. Each slot holds one Fiber + a timer entry in the
  # parallel `sched_wake_at` int array.
  @sched_fibers   = [Tep::FiberSlot.new(Fiber.new { Tep.seed_fiber_noop })]
  @sched_fibers.delete_at(0)
  @sched_wake_at  = [0]
  @sched_wake_at.delete_at(0)
  @sched_current  = -1               # currently-running fiber idx
                                     # (-1 = scheduler root).
  # Parallel I/O-wait arrays. `sched_io_fd[i] == -1` means the
  # fiber isn't parked on I/O (pure timer wait, or ready). When
  # parked: `sched_io_mode[i]` carries the requested READ/WRITE
  # bits, and tick() writes back the observed-ready bits into
  # `sched_io_ready[i]`. io_wait returns those bits to its caller.
  @sched_io_fd    = [0]
  @sched_io_fd.delete_at(0)
  @sched_io_mode  = [0]
  @sched_io_mode.delete_at(0)
  @sched_io_ready = [0]
  @sched_io_ready.delete_at(0)
  @user_bg_started   = false
end

Instance Attribute Details

#after_filterObject

Returns the value of attribute after_filter.



14
15
16
# File 'lib/tep/app.rb', line 14

def after_filter
  @after_filter
end

#asset_bodiesObject

Returns the value of attribute asset_bodies.



65
66
67
# File 'lib/tep/app.rb', line 65

def asset_bodies
  @asset_bodies
end

#asset_etagsObject

Returns the value of attribute asset_etags.



65
66
67
# File 'lib/tep/app.rb', line 65

def asset_etags
  @asset_etags
end

#asset_mimesObject

Returns the value of attribute asset_mimes.



65
66
67
# File 'lib/tep/app.rb', line 65

def asset_mimes
  @asset_mimes
end

#auth_bearer_secretObject

Shared HS256 secret consumed by Tep::AuthBearerToken. Stored on APP (rather than a class var) so spinel routes the read through the canonical instance-attr path.



26
27
28
# File 'lib/tep/app.rb', line 26

def auth_bearer_secret
  @auth_bearer_secret
end

#auth_filterObject

The auth-filter runs BEFORE before_filter so handler bodies and user filters always see a populated req.identity. Separate slot (rather than wedging into before_filter) so user-installed filters and the auth populate don’t fight for the single slot tep otherwise imposes. Default is a no-op Tep::Filter; the Auth battery installs Tep::AuthFilter on top via Tep::Auth.install!.



22
23
24
# File 'lib/tep/app.rb', line 22

def auth_filter
  @auth_filter
end

#auth_oauth2_clientsObject

Per-process OAuth2 client registry + ephemeral authorization-code store. See Tep::AuthOAuth2 for the issuance flow.



29
30
31
# File 'lib/tep/app.rb', line 29

def auth_oauth2_clients
  @auth_oauth2_clients
end

#auth_oauth2_codesObject

Returns the value of attribute auth_oauth2_codes.



30
31
32
# File 'lib/tep/app.rb', line 30

def auth_oauth2_codes
  @auth_oauth2_codes
end

#before_filterObject

Returns the value of attribute before_filter.



14
15
16
# File 'lib/tep/app.rb', line 14

def before_filter
  @before_filter
end

#broadcast_pg_channelObject

Returns the value of attribute broadcast_pg_channel.



53
54
55
# File 'lib/tep/app.rb', line 53

def broadcast_pg_channel
  @broadcast_pg_channel
end

#broadcast_pg_connObject

Returns the value of attribute broadcast_pg_conn.



54
55
56
# File 'lib/tep/app.rb', line 54

def broadcast_pg_conn
  @broadcast_pg_conn
end

#broadcast_pg_enabledObject

PG-backed cross-worker pub/sub state. ‘broadcast_pg_enabled` is 0 when off, 1 when on. The dedicated LISTEN connection lives in `broadcast_pg_conn`; channel name in `broadcast_pg_channel`. Configured by Tep::Broadcast.enable_pg_backend.



52
53
54
# File 'lib/tep/app.rb', line 52

def broadcast_pg_enabled
  @broadcast_pg_enabled
end

#broadcast_subsObject

Per-process Broadcast subscriber registry. Each entry pairs a topic with an output fd; publish iterates + writes the payload to every matching fd.



34
35
36
# File 'lib/tep/app.rb', line 34

def broadcast_subs
  @broadcast_subs
end

#nf_handlerObject

Returns the value of attribute nf_handler.



14
15
16
# File 'lib/tep/app.rb', line 14

def nf_handler
  @nf_handler
end

#openai_backendObject

Tep::Llm::OpenAI::Server backend (Battery 7). Set by Server.use(backend) at boot; the route handlers dispatch through it per request. Seeded with a base Backend in lib/tep.rb (after openai_server.rb loads – not in initialize, since the class isn’t defined yet there), same pattern as broadcast_pg_conn.



60
61
62
# File 'lib/tep/app.rb', line 60

def openai_backend
  @openai_backend
end

#openai_eventsObject

Tep::Events emitter for the openai-server (7.1c). Configured by Server.serve!(events_jsonl); empty path => zero-overhead disabled. Late-seeded for the same reason as openai_backend.



64
65
66
# File 'lib/tep/app.rb', line 64

def openai_events
  @openai_events
end

#presence_entriesObject

Per-process Presence entry registry. Each entry is one (principal, session, topic) tracking, with kind/agent_id + structured-status fields inline. See Tep::Presence.



38
39
40
# File 'lib/tep/app.rb', line 38

def presence_entries
  @presence_entries
end

#presence_pg_connObject

Returns the value of attribute presence_pg_conn.



46
47
48
# File 'lib/tep/app.rb', line 46

def presence_pg_conn
  @presence_pg_conn
end

#presence_pg_enabledObject

PG-mirror state for cross-worker visibility. ‘enabled` is 0 when off, 1 when on. `worker_id` uniquely identifies this worker’s rows in the tep_presence table (PID + boot epoch so a restart on the same PID isn’t aliased). See Tep::Presence.enable_pg_mirror.



44
45
46
# File 'lib/tep/app.rb', line 44

def presence_pg_enabled
  @presence_pg_enabled
end

#presence_pg_worker_idObject

Returns the value of attribute presence_pg_worker_id.



45
46
47
# File 'lib/tep/app.rb', line 45

def presence_pg_worker_id
  @presence_pg_worker_id
end

#routerObject

Returns the value of attribute router.



12
13
14
# File 'lib/tep/app.rb', line 12

def router
  @router
end

#sched_currentObject

Returns the value of attribute sched_current.



66
67
68
# File 'lib/tep/app.rb', line 66

def sched_current
  @sched_current
end

#sched_fibersObject

Returns the value of attribute sched_fibers.



66
67
68
# File 'lib/tep/app.rb', line 66

def sched_fibers
  @sched_fibers
end

#sched_io_fdObject

Returns the value of attribute sched_io_fd.



67
68
69
# File 'lib/tep/app.rb', line 67

def sched_io_fd
  @sched_io_fd
end

#sched_io_modeObject

Returns the value of attribute sched_io_mode.



67
68
69
# File 'lib/tep/app.rb', line 67

def sched_io_mode
  @sched_io_mode
end

#sched_io_readyObject

Returns the value of attribute sched_io_ready.



67
68
69
# File 'lib/tep/app.rb', line 67

def sched_io_ready
  @sched_io_ready
end

#sched_wake_atObject

Returns the value of attribute sched_wake_at.



66
67
68
# File 'lib/tep/app.rb', line 66

def sched_wake_at
  @sched_wake_at
end

#session_secretObject

Returns the value of attribute session_secret.



12
13
14
# File 'lib/tep/app.rb', line 12

def session_secret
  @session_secret
end

#static_rootObject

Returns the value of attribute static_root.



12
13
14
# File 'lib/tep/app.rb', line 12

def static_root
  @static_root
end

#tls_certObject

Returns the value of attribute tls_cert.



13
14
15
# File 'lib/tep/app.rb', line 13

def tls_cert
  @tls_cert
end

#tls_keyObject

Returns the value of attribute tls_key.



13
14
15
# File 'lib/tep/app.rb', line 13

def tls_key
  @tls_key
end

#user_bg_startedObject

Tep::Job background-worker idempotency flag. App-level so a single-shot spawn from a before-filter doesn’t fire repeatedly. Per-worker (each prefork child has its own Tep::APP, so each worker spawns one background fiber).



72
73
74
# File 'lib/tep/app.rb', line 72

def user_bg_started
  @user_bg_started
end

Class Method Details

.guess_mime(path) ⇒ Object



275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
# File 'lib/tep/app.rb', line 275

def self.guess_mime(path)
  lower = path.downcase
  if lower.end_with?(".html") || lower.end_with?(".htm")
    return "text/html; charset=utf-8"
  end
  if lower.end_with?(".css");  return "text/css"; end
  if lower.end_with?(".js");   return "application/javascript"; end
  if lower.end_with?(".json"); return "application/json"; end
  if lower.end_with?(".png");  return "image/png"; end
  if lower.end_with?(".jpg") || lower.end_with?(".jpeg"); return "image/jpeg"; end
  if lower.end_with?(".gif");  return "image/gif"; end
  if lower.end_with?(".svg");  return "image/svg+xml"; end
  if lower.end_with?(".txt");  return "text/plain; charset=utf-8"; end
  "application/octet-stream"
end

Instance Method Details

#add_asset(path, body, mime) ⇒ Object



133
134
135
136
137
138
139
140
141
142
143
# File 'lib/tep/app.rb', line 133

def add_asset(path, body, mime)
  @asset_bodies[path] = body
  @asset_mimes[path]  = mime
  # Content-hash ETag for cache revalidation (#152). SHA-1 is used
  # purely as a fast content fingerprint here (not a security hash --
  # collision resistance is irrelevant for an ETag, same as git's
  # content addressing). Computed once at boot. (Binary bodies with
  # embedded NULs hash by their leading bytes via the FFI string
  # boundary; still stable per content, which is all an ETag needs.)
  @asset_etags[path] = Crypto.sp_crypto_sha1_hex(body)
end

#add_route(verb, pattern, handler) ⇒ Object



159
160
161
# File 'lib/tep/app.rb', line 159

def add_route(verb, pattern, handler)
  @router.add(verb, pattern, handler)
end

#dispatch(req, res) ⇒ Object



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# File 'lib/tep/app.rb', line 178

def dispatch(req, res)
  # Pull a signed session cookie into req.session, when configured.
  secret = Tep.session_secret
  if secret.length > 0
    cv = req.cookies[Tep::COOKIE_NAME]
    if cv.length > 0
      req.session.load_from(cv, secret)
    end
  end

  asset_served = false
  # Auth filter populates req.identity (anonymous or matched
  # provider's Identity) before the user's before-filter runs,
  # so user code can always rely on req.identity being set.
  @auth_filter.before(req, res)
  if res.halted
    # Auth filter signalled "deny" -- skip the user filter +
    # route dispatch, fall through to after-filter + session.
  end
  @before_filter.before(req, res)
  if !res.halted
    # Bundled assets (everything under <app>/assets/, baked into
    # the binary by bin/tep) take precedence over the route
    # table. Match by exact path; on hit we set the body + ct
    # and skip route dispatch + 404 fallback. The after-filter
    # and session cookie writing still run normally.
    if Tep::Assets.serve(req.path, res)
      asset_served = true
    end
  end
  if !res.halted && !asset_served
    route = @router.match(req)
    # `pass` loop: a handler can signal skip-to-next-route by
    # setting req.passed. Iterate until a handler doesn't pass,
    # or we run out of matching routes.
    served = false
    while route != nil && !served
      route.fold_captures(req)
      req.passed = false
      out = route.handler.handle(req, res)
      if req.passed
        idx   = @router.index_of(route)
        route = @router.match_after(req, idx)
      else
        res.set_body_if_empty(out)
        served = true
      end
    end
    if !served
      if !try_static(req, res)
        out = @nf_handler.handle(req, res)
        res.set_status(404)
        if out.length > 0
          res.set_body_if_empty(out)
        else
          res.set_body_if_empty("<h1>404 Not Found</h1><p>" +
                                req.verb + " " + req.path + "</p>\n")
        end
      end
    end
  end
  @after_filter.after(req, res)

  # If the handler / filters mutated the session, sign + emit a
  # Set-Cookie line. Path=/ so the cookie applies to the whole
  # app; HttpOnly to keep it out of JS.
  secret_w = Tep.session_secret
  if secret_w.length > 0 && req.session.dirty
    opts = Tep.str_hash
    opts["Path"]      = "/"
    opts["HttpOnly"]  = ""
    opts["SameSite"]  = "Lax"
    res.set_cookie(Tep::COOKIE_NAME, req.session.to_cookie_value(secret_w), opts)
  end
end

#set_after(f) ⇒ Object



165
# File 'lib/tep/app.rb', line 165

def set_after(f);             @after_filter = f; end

#set_auth_bearer_secret(s) ⇒ Object



167
# File 'lib/tep/app.rb', line 167

def set_auth_bearer_secret(s); @auth_bearer_secret = s; end

#set_auth_filter(f) ⇒ Object



166
# File 'lib/tep/app.rb', line 166

def set_auth_filter(f);       @auth_filter = f; end

#set_before(f) ⇒ Object



164
# File 'lib/tep/app.rb', line 164

def set_before(f);            @before_filter = f; end

#set_broadcast_pg_channel(s) ⇒ Object



169
# File 'lib/tep/app.rb', line 169

def set_broadcast_pg_channel(s); @broadcast_pg_channel = s; end

#set_broadcast_pg_conn(c) ⇒ Object



170
# File 'lib/tep/app.rb', line 170

def set_broadcast_pg_conn(c);    @broadcast_pg_conn    = c; end

#set_broadcast_pg_enabled(v) ⇒ Object



168
# File 'lib/tep/app.rb', line 168

def set_broadcast_pg_enabled(v); @broadcast_pg_enabled = v; end

#set_not_found(h) ⇒ Object



176
# File 'lib/tep/app.rb', line 176

def set_not_found(h);         @nf_handler = h; end

#set_openai_backend(b) ⇒ Object



174
# File 'lib/tep/app.rb', line 174

def set_openai_backend(b);        @openai_backend        = b; end

#set_openai_events(e) ⇒ Object



175
# File 'lib/tep/app.rb', line 175

def set_openai_events(e);         @openai_events         = e; end

#set_presence_pg_conn(c) ⇒ Object



173
# File 'lib/tep/app.rb', line 173

def set_presence_pg_conn(c);      @presence_pg_conn      = c; end

#set_presence_pg_enabled(v) ⇒ Object



171
# File 'lib/tep/app.rb', line 171

def set_presence_pg_enabled(v);   @presence_pg_enabled   = v; end

#set_presence_pg_worker_id(s) ⇒ Object



172
# File 'lib/tep/app.rb', line 172

def set_presence_pg_worker_id(s); @presence_pg_worker_id = s; end

#set_session_secret(s) ⇒ Object



145
146
147
# File 'lib/tep/app.rb', line 145

def set_session_secret(s)
  @session_secret = s
end

#set_static_root(root) ⇒ Object



163
# File 'lib/tep/app.rb', line 163

def set_static_root(root);    @static_root = root; end

#set_tls_cert(s) ⇒ Object

Inbound TLS cert/key paths (tep#148 phase 2). Set via Tep.tls_cert= / Tep.tls_key=; read by Tep::Server.run at boot.



151
152
153
# File 'lib/tep/app.rb', line 151

def set_tls_cert(s)
  @tls_cert = s
end

#set_tls_key(s) ⇒ Object



155
156
157
# File 'lib/tep/app.rb', line 155

def set_tls_key(s)
  @tls_key = s
end

#try_static(req, res) ⇒ Object



254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/tep/app.rb', line 254

def try_static(req, res)
  if @static_root.length == 0
    return false
  end
  if req.verb != "GET" && req.verb != "HEAD"
    return false
  end
  if Tep.str_find(req.path, "..", 0) >= 0
    return false
  end
  full = @static_root + req.path
  sz = Sock.sphttp_filesize(full)
  if sz < 0
    return false
  end
  res.headers["Content-Type"] = App.guess_mime(full)
  res.headers["X-Tep-Static"] = "1"
  res.send_file(full)
  true
end