-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathmodule.ae
More file actions
855 lines (773 loc) · 34.3 KB
/
Copy pathmodule.ae
File metadata and controls
855 lines (773 loc) · 34.3 KB
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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
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
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
// std.http - HTTP Client & Server (alias for std.net)
//
// This is a convenience module that re-exports HTTP functions from std.net.
// You can use either:
// import std.http (this module)
// import std.net (consolidated networking module)
//
// The implementations are in std/net/aether_http.c and std/net/aether_http_server.c
// which are compiled into libaether.a
exports(
http_get_raw, http_get_with_timeout_raw, http_get_with_timeout_ns_raw,
http_post_raw, http_put_raw, http_delete_raw,
http_response_free, http_response_status_code,
http_response_body_str, http_response_headers_str,
http_response_status, http_response_body, http_response_headers,
http_response_error, http_response_ok,
get, get_with_timeout, post, put, delete,
http_server_create, http_server_bind_raw, http_server_port, http_server_start_raw,
http_server_set_host, server_set_host,
http_server_start_background_raw,
http_server_stop, http_server_free,
http_server_set_tls_raw, server_set_tls,
http_server_set_keepalive_raw, server_set_keepalive,
http_server_set_h2_raw, server_set_h2,
http_server_set_h2_concurrent_dispatch_raw, server_set_h2_concurrent_dispatch,
http_server_drain_connection,
http_server_shutdown_graceful_raw, server_shutdown_graceful,
http_server_set_on_start, http_server_set_on_stop,
server_on_start, server_on_stop,
http_server_set_health_probes_raw, server_set_health_probes,
http_server_set_access_log_raw, server_set_access_log,
http_server_set_metrics_raw, server_set_metrics,
http_server_sse, server_sse,
http_sse_send_event, http_sse_send_event_id, http_sse_close,
sse_send, sse_send_id, sse_close,
http_server_websocket, server_websocket,
http_ws_send_text, http_ws_send_binary, http_ws_recv,
http_ws_message_data, http_ws_message_length, http_ws_close,
ws_send_text, ws_send_binary, ws_recv,
ws_message, ws_message_length, ws_close,
server_bind, server_start, server_start_background,
http_server_add_route, http_server_get, http_server_post,
http_server_put, http_server_delete,
http_server_use_middleware,
http_get_header, http_get_query_param, http_get_path_param,
http_request_free,
http_response_create, http_response_set_status,
http_response_set_header, http_response_add_header,
http_response_clear_headers,
http_response_set_body, http_response_set_body_n,
http_response_accept_tunnel, response_accept_tunnel,
http_response_json, http_server_response_free,
http_server_set_actor_handler,
http_request_method, http_request_path, http_request_body,
http_request_body_length, http_request_query,
http_request_remote_addr, http_request_remote_port,
http_request_local_addr, http_request_local_port,
http_request_scheme, http_request_is_tls, http_request_http_version,
http_request_body_read_raw, http_get_request_body_read,
http_get_request_body_read_length, http_release_request_body_read,
http_request_body_complete,
request_body_read, request_body_complete,
http_request_header_count, http_request_header_name, http_request_header_value,
http_serve_static, http_serve_file, http_mime_type,
serve_file, serve_static,
request_method, request_path, request_body,
request_body_length, request_query,
request_remote_addr, request_remote_port,
request_local_addr, request_local_port,
request_scheme, request_is_tls, request_http_version,
request_header_count, request_header_name, request_header_value,
ae_io_await, ae_io_cancel, await_io
)
// HTTP Client — raw ptr-returning externs.
// These are the escape hatch for code that needs direct access to the
// underlying HttpResponse struct (e.g. to inspect headers and body
// together, or to implement custom retry logic). Most callers should
// use the Go-style wrappers below.
extern http_get_raw(url: string) -> ptr
// http.get with a per-call timeout. 0ns blocks forever
// (matches http.get's default). Sub-second values are rounded UP to
// whole seconds because the underlying SO_RCVTIMEO field is integer
// seconds. Use this any time the target URL isn't local — a hung
// DNS lookup or unresponsive server otherwise stalls the calling
// actor's entire message handler.
extern http_get_with_timeout_raw(url: string, timeout_ms: int) -> ptr
extern http_get_with_timeout_ns_raw(url: string, timeout: Duration) -> ptr
extern http_post_raw(url: string, body: string, content_type: string) -> ptr
extern http_put_raw(url: string, body: string, content_type: string) -> ptr
extern http_delete_raw(url: string) -> ptr
extern http_response_free(response: ptr)
extern http_response_status_code(response: ptr) -> int
extern http_response_body_str(response: ptr) -> string
extern http_response_headers_str(response: ptr) -> string
// HTTP Client - response accessors (used by both raw callers and the
// Go-style wrappers below).
extern http_response_status(response: ptr) -> int
extern http_response_body(response: ptr) -> string @heap
extern http_response_headers(response: ptr) -> string
extern http_response_error(response: ptr) -> string
extern http_response_ok(response: ptr) -> int
// string_concat used internally to duplicate borrowed strings before the
// backing response is freed. The concat of any string with "" produces a
// new heap-allocated copy that outlives the source.
extern string_concat(a: string, b: string) -> string
// HTTP Client — Go-style result-type wrappers. These are the idiomatic
// Aether-facing API: they auto-free the underlying response and return
// `(body, err)` or `(status, err)` tuples.
//
// On success, the error string is empty ("") and the value is valid.
// On failure, the value is a zero-value ("" or 0) and the error string
// describes what went wrong. Errors include both transport-level
// failures (DNS, connect, send) and non-2xx HTTP status codes.
// GET a URL and return the response body. Most common case.
get(url: string) -> {
response = http_get_raw(url)
if response == null {
return "", "out of memory"
}
err = http_response_error(response)
if err != "" {
err_copy = string_concat(err, "")
http_response_free(response)
return "", err_copy
}
status = http_response_status(response)
if status < 200 {
http_response_free(response)
return "", "unexpected status"
}
if status >= 300 {
http_response_free(response)
return "", "http error"
}
body = http_response_body(response)
body_copy = string_concat(body, "")
http_response_free(response)
return body_copy, ""
}
// GET a URL with a per-call timeout. Same return shape
// as `get`. Use this for any third-party URL — without it, a hung
// site stalls the calling actor's whole message handler. 0ns
// preserves `get`'s "block forever" default; values <1000 are rounded
// up to 1 second under the hood (the underlying socket-timeout field
// is integer seconds today).
get_with_timeout(url: string, timeout: Duration) -> {
response = http_get_with_timeout_ns_raw(url, timeout)
if response == null {
return "", "out of memory"
}
err = http_response_error(response)
if err != "" {
err_copy = string_concat(err, "")
http_response_free(response)
return "", err_copy
}
status = http_response_status(response)
if status < 200 {
http_response_free(response)
return "", "unexpected status"
}
if status >= 300 {
http_response_free(response)
return "", "http error"
}
body = http_response_body(response)
body_copy = string_concat(body, "")
http_response_free(response)
return body_copy, ""
}
// POST a body and return the response body.
post(url: string, body: string, content_type: string) -> {
response = http_post_raw(url, body, content_type)
if response == null {
return "", "out of memory"
}
err = http_response_error(response)
if err != "" {
err_copy = string_concat(err, "")
http_response_free(response)
return "", err_copy
}
status = http_response_status(response)
if status < 200 {
http_response_free(response)
return "", "unexpected status"
}
if status >= 300 {
http_response_free(response)
return "", "http error"
}
result = http_response_body(response)
result_copy = string_concat(result, "")
http_response_free(response)
return result_copy, ""
}
// PUT a body and return the response body.
put(url: string, body: string, content_type: string) -> {
response = http_put_raw(url, body, content_type)
if response == null {
return "", "out of memory"
}
err = http_response_error(response)
if err != "" {
err_copy = string_concat(err, "")
http_response_free(response)
return "", err_copy
}
status = http_response_status(response)
if status < 200 {
http_response_free(response)
return "", "unexpected status"
}
if status >= 300 {
http_response_free(response)
return "", "http error"
}
result = http_response_body(response)
result_copy = string_concat(result, "")
http_response_free(response)
return result_copy, ""
}
// DELETE a URL and return the response body.
delete(url: string) -> {
response = http_delete_raw(url)
if response == null {
return "", "out of memory"
}
err = http_response_error(response)
if err != "" {
err_copy = string_concat(err, "")
http_response_free(response)
return "", err_copy
}
status = http_response_status(response)
if status < 200 {
http_response_free(response)
return "", "unexpected status"
}
if status >= 300 {
http_response_free(response)
return "", "http error"
}
result = http_response_body(response)
result_copy = string_concat(result, "")
http_response_free(response)
return result_copy, ""
}
// HTTP Server - lifecycle
extern http_server_create(port: int) -> ptr
extern http_server_bind_raw(server: ptr, host: string, port: int) -> int
// Resolved listening port. After binding with port 0 (OS-assigned),
// returns the kernel-chosen port so dynamic-port users can discover it;
// otherwise echoes the requested port. 0 if unbound.
extern http_server_port(server: ptr) -> int
// Set the bind address before server_start. Default is "0.0.0.0"
// (all interfaces). Pass "127.0.0.1" to bind loopback only — useful
// for tests because macOS / Windows firewalls don't prompt on
// loopback binds.
extern http_server_set_host(server: ptr, host: string)
extern http_server_start_raw(server: ptr) -> int
extern http_server_start_background_raw(server: ptr) -> int
extern http_server_stop(server: ptr)
extern http_server_free(server: ptr)
// TLS termination (#260 Tier 0). Loads the cert+key (PEM-encoded);
// after this call every accepted connection completes a TLS handshake
// before the HTTP parse runs. Returns "" on success, error string
// otherwise. See server_set_tls below for the Aether-side wrapper.
extern http_server_set_tls_raw(server: ptr, cert_path: string, key_path: string) -> string
// Enable TLS termination on an HTTP server. Both paths must point at
// PEM-encoded files; the cert and key are validated to match before
// the server is marked TLS-enabled. Returns "" on success, an error
// string on failure (file unreadable, parse error, mismatched key,
// build without OpenSSL).
//
// Idempotent — calling twice with the same files is a no-op success;
// calling with different files re-loads.
//
// Example:
// server = http.server_create(443)
// err = http.server_set_tls(server, "/etc/ssl/cert.pem", "/etc/ssl/key.pem")
// if err != "" { println("TLS setup failed: ${err}"); exit(1) }
// http.server_start(server)
server_set_tls(server: ptr, cert_path: string, key_path: string) -> {
return http_server_set_tls_raw(server, cert_path, key_path)
}
// HTTP/2 (#260 Tier 2). When enabled:
// - the TLS context advertises "h2" via ALPN with "http/1.1" as
// the fallback, so HTTP/2-capable clients (curl --http2,
// browsers, fan-out gateways) negotiate the framed protocol
// while older clients stay on HTTP/1.1 transparently;
// - plain (non-TLS) connections honour h2c upgrade requests
// per RFC 7540 §3.2 (Upgrade: h2c + HTTP2-Settings header);
// - per-stream dispatch reuses the existing route table, so all
// middleware / metrics / access logs / health probes / response
// transformers (gzip, error_pages) apply uniformly across
// HTTP/1.1 and HTTP/2 traffic.
//
// max_concurrent_streams advertises SETTINGS_MAX_CONCURRENT_STREAMS
// to the peer. Pass 0 for libnghttp2's default (100). Negative
// values return an error.
//
// Returns "" on success or an error string when the build doesn't
// link libnghttp2 (the rest of the server keeps working in
// HTTP/1.1-only mode).
//
// Example (h2 over TLS):
// server = http.server_create(443)
// err1 = http.server_set_tls(server, "cert.pem", "key.pem")
// err2 = http.server_set_h2(server, 0) // 0 = nghttp2 default
// if err1 != "" { println("TLS: ${err1}"); exit(1) }
// if err2 != "" { println("h2: ${err2}"); exit(1) }
// http.server_start(server)
extern http_server_set_h2_raw(server: ptr, max_concurrent_streams: int) -> string
server_set_h2(server: ptr, max_concurrent_streams: int) -> {
return http_server_set_h2_raw(server, max_concurrent_streams)
}
// Per-stream concurrent dispatch for HTTP/2. When `worker_count`
// is greater than 0, each h2 connection spawns this many worker
// threads so multiple streams can run their handlers in parallel.
// Default 0 = sequential dispatch on the connection thread (legacy
// behaviour). Returns "" on success, error string otherwise.
//
// workers typical use
// ------- --------------------------------------------------
// 0 keep handlers on the connection thread (default)
// 4 small fan-out — most servers
// 8–16 heavy fan-out — handler runs DB queries / large I/O
// >16 diminishing returns; bound by the connection accept
// queue and the number of physical cores
extern http_server_set_h2_concurrent_dispatch_raw(server: ptr, worker_count: int) -> string
server_set_h2_concurrent_dispatch(server: ptr, worker_count: int) -> {
return http_server_set_h2_concurrent_dispatch_raw(server, worker_count)
}
// HTTP/1.1 keep-alive (#260 Tier 0). When enabled, the server reads
// multiple requests off one socket instead of closing after each.
// max_requests = 0 -> unlimited per connection
// idle_timeout = 0ns -> use the default 30s SO_RCVTIMEO
// Returns "" on success.
extern http_server_set_keepalive_raw(server: ptr, enabled: int, max_requests: int, idle_ns: long) -> string
server_set_keepalive(server: ptr, enabled: int, max_requests: int, idle_timeout: Duration) -> {
return http_server_set_keepalive_raw(server, enabled, max_requests, idle_timeout.ns)
}
// Graceful shutdown (#260 Tier 3). Stops accepting new connections,
// then waits up to `timeout` for in-flight ones to finish naturally.
// Returns "" on clean drain, "timeout" if connections were still
// active when the deadline passed. Typically wired into a SIGTERM
// handler in the entry point.
extern http_server_shutdown_graceful_raw(server: ptr, timeout_ns: long) -> string
server_shutdown_graceful(server: ptr, timeout: Duration) -> {
return http_server_shutdown_graceful_raw(server, timeout.ns)
}
// Lifecycle hooks (#260 Tier 3). on_start fires once after the listen
// socket is bound, before the accept loop runs (great place to log
// "ready" or flip a readiness probe). on_stop fires after the accept
// loop exits but before sockets close. Both pass the `user_data`
// pointer to the hook function. Hooks are C function pointers — declare
// them with @c_callback on the Aether side.
extern http_server_set_on_start(server: ptr, hook: ptr, user_data: ptr)
extern http_server_set_on_stop(server: ptr, hook: ptr, user_data: ptr)
server_on_start(server: ptr, hook: ptr, user_data: ptr) {
http_server_set_on_start(server, hook, user_data)
}
server_on_stop(server: ptr, hook: ptr, user_data: ptr) {
http_server_set_on_stop(server, hook, user_data)
}
// Health-probe endpoints (#260 Tier 3). Registers two routes:
// live_path — always 200 "ok" (process is up)
// ready_path — calls ready_check (1 -> 200 "ready", 0 -> 503 "not ready")
// Either path may be "" to skip registration. ready_check may be null —
// in that case ready_path returns 200 too.
extern http_server_set_health_probes_raw(server: ptr,
live_path: string,
ready_path: string,
ready_check: ptr,
user_data: ptr) -> string
server_set_health_probes(server: ptr, live_path: string, ready_path: string,
ready_check: ptr, user_data: ptr) -> {
return http_server_set_health_probes_raw(server, live_path, ready_path,
ready_check, user_data)
}
// Built-in access logger (#260 Tier 3 / F1). format: "combined" or
// "json". output_path: file path (opened with "ab" — appends),
// "-" for stderr, or "" to disable. Returns "" on success.
extern http_server_set_access_log_raw(server: ptr, format: string, output_path: string) -> string
server_set_access_log(server: ptr, format: string, output_path: string) -> {
return http_server_set_access_log_raw(server, format, output_path)
}
// Built-in per-route metrics (#260 Tier 3 / F2). Tracks request
// counts, error rates, latency histograms; exposes the standard
// Prometheus text format on the configured endpoint (default
// "/metrics"). Returns "" on success.
extern http_server_set_metrics_raw(server: ptr, metrics_endpoint: string) -> string
server_set_metrics(server: ptr, metrics_endpoint: string) -> {
return http_server_set_metrics_raw(server, metrics_endpoint)
}
// Server-Sent Events (#260 Tier 2). Register an SSE route — the
// handler owns the connection lifetime and pushes events directly
// to the client. The server sends `Content-Type: text/event-stream`
// + `Connection: close` automatically before invoking the handler.
//
// Handler signature (declared as @c_callback on the Aether side):
// handler(req: ptr, sse: ptr, ud: ptr)
//
// Inside the handler, call sse_send / sse_send_id to push events,
// or sse_close to terminate explicitly. The connection closes
// when the handler returns.
extern http_server_sse(server: ptr, path: string, handler: ptr, user_data: ptr)
extern http_sse_send_event(sse: ptr, event_name: string, data: string) -> int
extern http_sse_send_event_id(sse: ptr, event_name: string, data: string, id: string) -> int
extern http_sse_close(sse: ptr)
server_sse(server: ptr, path: string, handler: ptr, user_data: ptr) {
http_server_sse(server, path, handler, user_data)
}
sse_send(sse: ptr, event_name: string, data: string) -> int {
return http_sse_send_event(sse, event_name, data)
}
sse_send_id(sse: ptr, event_name: string, data: string, id: string) -> int {
return http_sse_send_event_id(sse, event_name, data, id)
}
sse_close(sse: ptr) {
http_sse_close(sse)
}
// WebSocket (#260 Tier 2 / E2 — RFC 6455). The handler owns the
// connection lifetime — the server completes the upgrade handshake
// then hands control to the user.
//
// Handler signature (declared @c_callback on the Aether side):
// handler(req: ptr, ws: ptr, ud: ptr)
//
// Inside the handler:
// ws_recv(ws) -> (kind, data) where kind = 1 (text) / 2 (binary) /
// -1 (closed)
// ws_send_text(ws, text) -> int (0 OK, -1 transport error)
// ws_send_binary(ws, data, len) -> int
// ws_close(ws, code, reason) — 1000 = normal, 1001 = going away, ...
//
// Control frames (ping/pong) are auto-handled inside ws_recv.
extern http_server_websocket(server: ptr, path: string, handler: ptr, user_data: ptr)
extern http_ws_send_text(ws: ptr, text: string) -> int
extern http_ws_send_binary(ws: ptr, data: ptr, length: int) -> int
extern http_ws_recv(ws: ptr) -> int
extern http_ws_message_data(ws: ptr) -> string
extern http_ws_message_length(ws: ptr) -> int
extern http_ws_close(ws: ptr, code: int, reason: string)
server_websocket(server: ptr, path: string, handler: ptr, user_data: ptr) {
http_server_websocket(server, path, handler, user_data)
}
ws_send_text(ws: ptr, text: string) -> int {
return http_ws_send_text(ws, text)
}
ws_send_binary(ws: ptr, data: ptr, length: int) -> int {
return http_ws_send_binary(ws, data, length)
}
// Returns: 1 = text frame, 2 = binary, -1 = closed.
// After 1 or 2, the message is accessible via ws_message and
// ws_message_length. Pointers are valid until the next ws_recv /
// ws_send / ws_close call.
ws_recv(ws: ptr) -> int {
return http_ws_recv(ws)
}
ws_message(ws: ptr) -> string {
return http_ws_message_data(ws)
}
ws_message_length(ws: ptr) -> int {
return http_ws_message_length(ws)
}
ws_close(ws: ptr, code: int, reason: string) {
http_ws_close(ws, code, reason)
}
// Drain a single accepted connection through the full HTTP lifecycle:
// optional TLS handshake, request parsing loop with keep-alive, route
// dispatch, response emission, socket close. This is the building
// block that user-side actor step functions plug into when running
// the server in actor-dispatch mode (set via
// http_server_set_actor_handler) — the actor's step receives a
// MSG_HTTP_CONNECTION with a client_fd and should call this helper
// to get the same TLS / keep-alive / route-dispatch behaviour the
// thread-pool worker path uses. (#260 Tier 0 / Phase C3.)
extern http_server_drain_connection(server: ptr, client_fd: int)
// Go-style wrappers for server lifecycle. Return "" on success, error
// string on failure.
server_bind(server: ptr, host: string, port: int) -> {
rc = http_server_bind_raw(server, host, port)
if rc < 0 {
return "bind failed"
}
return ""
}
// Set the bind host before server_start. Default is "0.0.0.0".
// Pass "127.0.0.1" to bind loopback only.
server_set_host(server: ptr, host: string) -> {
http_server_set_host(server, host)
return ""
}
server_start(server: ptr) -> {
rc = http_server_start_raw(server)
if rc < 0 {
return "server start failed"
}
return ""
}
// Start a server on a detached native thread. Useful for integration
// tests and embedding hosts that need multiple local servers in one
// process without tying up the Aether actor scheduler.
server_start_background(server: ptr) -> {
rc = http_server_start_background_raw(server)
if rc < 0 {
return "server background start failed"
}
return ""
}
// HTTP Server - routing.
//
// The `user_data` slot is plumbed end-to-end (the C side stores it on
// the route and passes it back as the 3rd arg of the registered
// handler). To carry a per-route context — a DB handle, a config
// struct, an auth scheme — declare a struct, `malloc`+init it, and
// pass the resulting `ptr` as `user_data`. The handler casts the
// `ud` arg back via `ud as *YourStruct`. Worked example at
// `tests/integration/http_handler_user_data/server.ae` and in
// `docs/http-handler-context.md`. (#633)
//
// The slot is per-route, so a server can host routes with distinct
// contexts without coordination. Passing `0` for `user_data`
// continues to work — opting in is purely additive.
extern http_server_add_route(server: ptr, method: string, path: string, handler: ptr, user_data: ptr)
extern http_server_get(server: ptr, path: string, handler: ptr, user_data: ptr)
extern http_server_post(server: ptr, path: string, handler: ptr, user_data: ptr)
extern http_server_put(server: ptr, path: string, handler: ptr, user_data: ptr)
extern http_server_delete(server: ptr, path: string, handler: ptr, user_data: ptr)
// HTTP Server - middleware
extern http_server_use_middleware(server: ptr, middleware: ptr, user_data: ptr)
// HTTP Request - accessors
extern http_get_header(req: ptr, name: string) -> string
extern http_get_query_param(req: ptr, name: string) -> string
extern http_get_path_param(req: ptr, name: string) -> string
extern http_request_free(req: ptr)
// HTTP Response - building
extern http_response_create() -> ptr
extern http_response_set_status(res: ptr, code: int)
extern http_response_set_header(res: ptr, name: string, value: string)
// Append a header verbatim, even if a header with the same name already
// exists. Use for protocols that allow/require repeated headers — e.g.
// SVN's WebDAV uses 13+ `DAV:` headers per response. The replace-on-
// duplicate behaviour of `set_header` collapses these into the last
// value, breaking SVN clients. `add_header` preserves order and
// multiplicity through to the wire serializer.
extern http_response_add_header(res: ptr, name: string, value: string)
// Drop every header on the response, including the `Content-Type` and
// `Server` defaults injected by http_response_create. Used by VCR's
// dispatcher to reset to a blank slate before emitting the tape's
// recorded headers verbatim — record/replay must not have Aether's
// defaults overlaid on the captured response.
extern http_response_clear_headers(res: ptr)
extern http_response_set_body(res: ptr, body: string)
// Length-aware sibling — binary-safe set_body. Use when the body
// may contain embedded NULs (binary content, gzip output, packed
// binary). Plain http_response_set_body uses strlen() and
// truncates at the first NUL.
extern http_response_set_body_n(res: ptr, body: string, length: int)
// Send the current HTTP response immediately and take ownership of
// the underlying cleartext HTTP/1.1 connection as a std.tcp socket.
// Returns null if the response is not attached to a live connection,
// the connection is TLS-wrapped, or the HTTP parser has buffered
// bytes that a raw socket could not replay. The returned socket is
// caller-owned and must be closed with tcp.close.
extern http_response_accept_tunnel(res: ptr) -> ptr
extern http_response_json(res: ptr, json: string)
extern http_server_response_free(res: ptr)
response_accept_tunnel(res: ptr) -> ptr {
return http_response_accept_tunnel(res)
}
// HTTP Server - actor dispatch mode
extern http_server_set_actor_handler(server: ptr, step_fn: ptr, send_fn: ptr, spawn_fn: ptr, release_fn: ptr)
// HTTP Request - field accessors (for actor dispatch mode).
// Raw externs first; `http.request_*` wrappers below give callers
// the namespaced form so user code reads `http.request_path(req)`
// rather than the bare `http_request_path` raw symbol.
extern http_request_method(req: ptr) -> string
extern http_request_path(req: ptr) -> string
extern http_request_body(req: ptr) -> string
// Byte length of the request body — sibling of `http_request_body`.
// Returns 0 if the request has no body (or is null). Use this when
// the body may contain embedded NULs (binary uploads, gzipped JSON,
// svn PUT) — `string.length` on the body would `strlen` and truncate.
extern http_request_body_length(req: ptr) -> int
extern http_request_body_read_raw(req: ptr, offset: int, max: int) -> int
// #644: 1 once the whole body has arrived (streaming request: every
// declared byte pulled off the wire; buffered request: always 1 — the
// dispatcher had the payload before the handler ran). Lets a chunked-
// iteration loop detect "done" without comparing offsets against
// request_body_length.
extern http_request_body_complete(req: ptr) -> int
extern http_get_request_body_read() -> string
extern http_get_request_body_read_length() -> int
extern http_release_request_body_read()
extern string_new_with_length(data: string, length: int) -> ptr
extern http_request_query(req: ptr) -> string
// Trusted TCP peer address — the IP `getpeername(2)` reports for the
// connection that carried this request. Use this (NOT the
// `X-Forwarded-For` header) when building an in-app source-IP
// allow/deny list on a directly-exposed listener: clients can put
// any value they like into the header, but only the kernel's
// notion of the peer reflects reality. Behind a trusted reverse
// proxy, `std.http.middleware.use_real_ip` remains the right tool.
// Returns "" when unavailable (Unix-domain socket, syscall failed,
// or req is null). Dotted-quad for IPv4 ("203.0.113.7"); the
// inet_ntop AF_INET6 form for IPv6 ("2001:db8::1") — no brackets,
// no scope id.
extern http_request_remote_addr(req: ptr) -> string
// Companion port — 0 when the address is unavailable too.
extern http_request_remote_port(req: ptr) -> int
// Server-side socket name from `getsockname(2)`. Needed when the
// listener binds 0.0.0.0 and the handler wants to know which NIC
// fielded this request (admin-on-loopback, multi-tenant per-IP
// routing). Same return semantics as the remote-* pair.
extern http_request_local_addr(req: ptr) -> string
extern http_request_local_port(req: ptr) -> int
// Transport scheme — "https" when this connection was TLS-wrapped
// at accept/handshake time, "http" otherwise. Source of truth is
// the local conn->ssl pointer, not anything in the wire bytes;
// drives canonical-URL building, redirect targets, and cookie
// Secure-flag decisions in app code. Behind a TLS-terminating
// proxy use the X-Forwarded-Proto middleware instead — the value
// here is what THIS server saw.
extern http_request_scheme(req: ptr) -> string
// Same flag as a 0/1 int — saves a string compare for callers
// that just need a bool.
extern http_request_is_tls(req: ptr) -> int
// HTTP version on the request line — "HTTP/1.0", "HTTP/1.1", or
// "HTTP/2.0". Surfaces a field the parser already captured.
extern http_request_http_version(req: ptr) -> string
// Request-header iteration. `http_get_header` only does a named lookup;
// these enumerate every received header so a handler can capture an
// unknown set (e.g. a faithful record/replay recorder). Order is wire
// order with duplicates preserved — no sort/dedup. Index range is
// [0, http_request_header_count); out-of-range returns "". Same 50-header
// cap as parsing.
extern http_request_header_count(req: ptr) -> int
extern http_request_header_name(req: ptr, index: int) -> string
extern http_request_header_value(req: ptr, index: int) -> string
// Go-style wrappers for the request field accessors.
request_method(req: ptr) -> string {
return http_request_method(req)
}
request_path(req: ptr) -> string {
return http_request_path(req)
}
request_body(req: ptr) -> string {
return http_request_body(req)
}
request_body_length(req: ptr) -> int {
return http_request_body_length(req)
}
// Chunked-read access to the request body (#626). Reads up to `max`
// bytes from `offset` into an owned AetherString. Returns
// (bytes, n, "") on success — `n` may be less than `max` at EOF.
// Returns ("", 0, error) on failure.
//
// RAM-bounded for large uploads (#626). A body larger than the 16 KiB
// connection buffer is NOT buffered whole: the server parses headers,
// then this accessor pulls each window straight off the socket. Reads
// must be sequential (offset advances by the returned `n`, which the
// loop below does naturally). Small bodies stay fully buffered and
// support random-access offsets. Either way the loop below is
// identical — peak server memory for a large PUT is one window per
// connection, not the whole object.
//
// Pair with `request_body_length` to drive the loop:
//
// total = http.request_body_length(req)
// off = 0
// while off < total {
// chunk, n, err = http.request_body_read(req, off, 65536)
// if err != "" { ...handle... break }
// if n == 0 { break }
// fs.pwrite(out_file, chunk, n, off) // stream to disk
// off = off + n
// }
request_body_read(req: ptr, offset: int, max: int) -> {
ok = http_request_body_read_raw(req, offset, max)
if ok == 0 {
return "", 0, "request_body_read failed"
}
raw = http_get_request_body_read()
n = http_get_request_body_read_length()
owned = string_new_with_length(raw, n)
http_release_request_body_read()
return owned, n, ""
}
// #644: has the request body fully arrived? 1 once every declared byte
// has been received (streaming request: pulled off the wire by
// request_body_read / request_body; buffered request: always 1). The
// natural loop terminator for chunked iteration:
//
// off = 0
// while http.request_body_complete(req) == 0 {
// chunk, n, err = http.request_body_read(req, off, 65536)
// if err != "" { ...handle... break }
// if n == 0 { break }
// ...consume chunk...
// off = off + n
// }
request_body_complete(req: ptr) -> int {
return http_request_body_complete(req)
}
request_query(req: ptr) -> string {
return http_request_query(req)
}
request_remote_addr(req: ptr) -> string {
return http_request_remote_addr(req)
}
request_remote_port(req: ptr) -> int {
return http_request_remote_port(req)
}
request_local_addr(req: ptr) -> string {
return http_request_local_addr(req)
}
request_local_port(req: ptr) -> int {
return http_request_local_port(req)
}
request_scheme(req: ptr) -> string {
return http_request_scheme(req)
}
request_is_tls(req: ptr) -> int {
return http_request_is_tls(req)
}
request_http_version(req: ptr) -> string {
return http_request_http_version(req)
}
request_header_count(req: ptr) -> int {
return http_request_header_count(req)
}
request_header_name(req: ptr, index: int) -> string {
return http_request_header_name(req, index)
}
request_header_value(req: ptr, index: int) -> string {
return http_request_header_value(req, index)
}
// Static file serving — raw externs.
extern http_serve_static(req: ptr, res: ptr, base_dir: string)
extern http_serve_file(res: ptr, filepath: string)
extern http_mime_type(path: string) -> string
// Go-style wrappers for static file serving. Both return ""
// implicitly (the underlying externs populate res with status
// + headers + body / fd; failure paths set 4xx/5xx on res rather
// than returning an error string).
//
// Issue #383 zero-copy: serve_file stages the file via open+fstat
// when supported, lets the connection writer take the sendfile(2)
// fast path, and falls back to a buffered read for TLS / HTTP/2
// / Range requests / Windows. Callers see identical behaviour
// either way.
serve_file(res: ptr, filepath: string) {
http_serve_file(res, filepath)
}
serve_static(req: ptr, res: ptr, base_dir: string) {
http_serve_static(req, res, base_dir)
}
// Reactor-pattern async I/O. See std/net/module.ae for full docs.
// Suspends the current actor until fd is readable. On wake, the
// scheduler delivers `IoReady { fd: int, events: int }` to the
// actor's mailbox.
extern ae_io_await(fd: int) -> int
extern ae_io_cancel(fd: int)
await_io(fd: int) -> {
rc = ae_io_await(fd)
if rc < 0 {
return "await_io failed"
}
return ""
}