├── .gitattributes
├── .gitignore
├── test
├── setup-test-env.sh
├── static
│ ├── test.html
│ └── index.html
└── test-igropyr.sc
├── package.sc
├── LICENSE
├── README.md
├── otp.sc
└── http.sc
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.sc linguist-language=Scheme
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.sc~
2 | *.sc#*
3 | .#*.sc
4 |
5 | httpc.so
6 |
--------------------------------------------------------------------------------
/test/setup-test-env.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Igropyr test environment setup script
3 |
4 | # Set Chez Scheme file extension recognition
5 | export CHEZSCHEMELIBEXTS=.chezscheme.sls::.chezscheme.so:.ss::.so:.sls::.so:.scm::.so:.sch::.so:.sc::.so
6 |
7 | # Set Chez Scheme library path
8 | export CHEZSCHEMELIBDIRS=.:lib:../..
9 |
--------------------------------------------------------------------------------
/test/static/test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Dynamic Rendering Test
5 |
6 |
7 | Dynamic Rendering Test Success!
8 | This is a file served from the static directory
9 | Server uses OTP supervision tree architecture
10 |
11 |
--------------------------------------------------------------------------------
/package.sc:
--------------------------------------------------------------------------------
1 | (("name" . "igropyr")
2 | ("version" . "1.0.0")
3 | ("description" . "a high-performance async I/O Http Server built with OTP architecture, featuring powerful concurrent processing capabilities and fault tolerance")
4 | ("keywords"
5 | ("Scheme" "http-server" "async"))
6 | ("author"
7 | ("guenchi"))
8 | ("private" . #f)
9 | ("scripts"
10 | ("build" . "")
11 | ("run" . "scheme --script")
12 | ("test" . ""))
13 | ("dependencies")
14 | ("devDependencies"))
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright guenchi (c) 2018 - 2025
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Igropyr
2 |
3 | 
4 |
5 |
6 | ## Overview
7 |
8 | Igropyr is a high-performance Async I/O Http Server built with OTP (Open Telecom Platform) architecture, featuring powerful concurrent processing capabilities and fault tolerance.
9 |
10 | ## Architecture
11 |
12 | The server implements OTP principles for robust, scalable web services:
13 | - **Actor-based concurrency** for isolated, concurrent request handling
14 | - **Supervisor trees** for automatic process recovery
15 | - **Message passing** for inter-process communication
16 | - **Fault isolation** preventing cascading failures
17 |
18 |
19 | ## Core Features
20 |
21 | ### ⚡ High Concurrency
22 | - Powerful concurrent processing with async I/O
23 | - Actor model for efficient task distribution
24 | - Optimized for handling multiple simultaneous requests
25 |
26 | ### 🔄 Let It Crash Philosophy
27 | - Automatic failure recovery
28 | - Task redistribution on failures
29 | - Built-in fault tolerance mechanisms
30 |
31 | ### 🔥 Hot Reload
32 | - Hot-reloadable routes and modules
33 | - Zero-downtime upgrades
34 | - Dynamic module replacement
35 |
36 | ### Test
37 |
38 | Main dashboard interface providing:
39 | - Server status display
40 | - Quick access to all test endpoints
41 |
42 | ## Available Test Endpoints
43 |
44 | ### Basic Features
45 | - `/` - Main dashboard with server status
46 | - `/api/info` - System status information
47 | - `/api/user/:id` - User parameter testing
48 | - `/test/json` - JSON response testing
49 | - `/test/redirect` - Redirect functionality testing
50 | - `/test/dynamic` - Dynamic rendering testing
51 | - `/test/template` - Template engine testing
52 |
53 | ### Fault Tolerance Testing
54 | - `/api/crash` - 💥 Crash test (tests server recovery)
55 | - `/api/error` - ⚠️ Error handling test
56 | - `/api/slow` - ⏱️ Slow request simulation (2 seconds)
57 | - `/api/cpu` - 🔥 CPU intensive operation test
58 | - `/test/random-crash` - 🎲 Random crash simulation
59 | - `/health` - ❤️ Health check endpoint
60 |
61 |
62 | ## Status
63 |
64 | ✅ All functions checked and operational
65 |
66 | ## Version
67 |
68 | Current version: v1.0 - OTP Architecture
69 |
--------------------------------------------------------------------------------
/test/static/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Igropyr Http Server
5 |
99 |
100 |
101 |
102 |
🚀 Igropyr Http Server
103 |
v1.0 - OTP Architecture
104 |
105 |
106 | ✅ All functions checked
107 |
108 |
109 |
110 |
111 |
⚡ High Concurrency
112 |
Powerful concurrent processing with async I/O and Actor model
113 |
114 |
115 |
🔄 Let It Crash
116 |
Automatic failure recovery and task redistribution
117 |
118 |
119 |
🔥 Hot Reload
120 |
Hot-reloadable routes and modules with zero-downtime upgrades
121 |
122 |
123 |
124 |
125 |
Test Features
126 |
134 |
135 |
136 |
137 |
OTP Fault Tolerance Tests
138 |
146 |
147 |
148 |
149 | Server Time: Loading...
150 |
151 |
152 |
153 |
157 |
158 |
--------------------------------------------------------------------------------
/otp.sc:
--------------------------------------------------------------------------------
1 | ;;; Igropyr OTP Enhancement Module
2 | ;;; Implements true Let it crash and task redistribution
3 | ;;;
4 | ;;; Copyright 2025
5 |
6 | #!chezscheme
7 | (library (igropyr otp)
8 | (export
9 | ;; Supervisor API
10 | start-supervisor
11 | stop-supervisor
12 | supervisor-status
13 |
14 | ;; Worker management
15 | spawn-worker
16 | worker-count
17 |
18 | ;; OTP-enhanced HTTP server
19 | start-otp-http-server
20 | )
21 |
22 | (import
23 | (chezscheme)
24 | (chezerlang compat)
25 | (chezerlang internal)
26 | (chezerlang erlang))
27 |
28 | ;; ==================== Worker Process Pool Implementation ====================
29 |
30 | ;; Worker process - handles HTTP requests
31 | (define (http-worker id supervisor)
32 | (spawn&link
33 | (lambda ()
34 | (printf "[Worker ~a] Starting\n" id)
35 | ;; Set not to trap EXIT, let process truly crash
36 | (process-trap-exit #f)
37 |
38 | (let worker-loop ([request-count 0])
39 | (receive
40 | ;; Process HTTP request
41 | [#(http-request ,from ,ip ,op ,req ,res)
42 | ;; Don't use guard - let errors crash the process
43 | (printf "[Worker ~a] Processing request #~a\n" id (+ request-count 1))
44 |
45 | ;; Notify Supervisor of request being processed
46 | (send supervisor `#(processing ,self ,from ,ip ,op ,req ,res))
47 |
48 | ;; Process request - will crash if error occurs
49 | (process-http-request req res ip op)
50 |
51 | ;; Successfully completed, notify Supervisor
52 | (send supervisor `#(completed ,self ,from))
53 | (send from `#(request-completed ,self))
54 |
55 | ;; Continue processing next request
56 | (worker-loop (+ request-count 1))]
57 |
58 | ;; Stop signal
59 | [#(stop)
60 | (printf "[Worker ~a] Normal stop\n" id)
61 | (exit 'normal)]
62 |
63 | ;; Health check
64 | [#(ping ,from)
65 | (send from `#(pong ,self ,id))
66 | (worker-loop request-count)]
67 |
68 | [,msg
69 | (printf "[Worker ~a] Unknown message: ~a\n" id msg)
70 | (worker-loop request-count)])))))
71 |
72 | ;; Actual logic for processing HTTP requests
73 | (define (process-http-request req res ip op)
74 | ;; Don't use guard here, let errors directly crash the process
75 | (let ([path (request-path req)])
76 | (cond
77 | ;; Simulate random crash
78 | [(string=? path "/crash")
79 | (error 'http-worker "Simulated crash")]
80 |
81 | ;; Normal processing
82 | [else
83 | ;; Send response
84 | (send-http-response op res)])))
85 |
86 | ;; ==================== Supervisor Implementation ====================
87 |
88 | ;; Supervisor state record
89 | (define-record supervisor-state
90 | [workers] ; Worker process list
91 | [pending-tasks] ; Pending task queue
92 | [processing-tasks] ; Tasks being processed
93 | [restart-count] ; Restart count
94 | [max-restarts] ; Maximum restart count
95 | [restart-window]) ; Restart time window (seconds)
96 |
97 | ;; Supervisor process - manages Worker lifecycle
98 | (define (supervisor-process worker-count)
99 | (spawn
100 | (lambda ()
101 | (printf "[Supervisor] Starting, managing ~a workers\n" worker-count)
102 | ;; Supervisor needs to trap EXIT messages
103 | (process-trap-exit #t)
104 |
105 | ;; Initialize state
106 | (let ([state (make-supervisor-state
107 | '() ; workers
108 | '() ; pending-tasks
109 | (make-hashtable equal-hash equal?) ; processing-tasks
110 | 0 ; restart-count
111 | 10 ; max-restarts
112 | 60)]) ; restart-window (seconds)
113 |
114 | ;; Create initial Worker pool
115 | (supervisor-state-workers-set! state
116 | (map (lambda (id)
117 | (let ([worker (http-worker id self)])
118 | (monitor worker) ; Monitor Worker
119 | (cons id worker)))
120 | (iota worker-count)))
121 |
122 | ;; Supervisor main loop
123 | (supervisor-loop state)))))
124 |
125 | ;; Supervisor main loop
126 | (define (supervisor-loop state)
127 | (receive
128 | ;; New HTTP request
129 | [#(http-request ,from ,ip ,op ,req ,res)
130 | (handle-new-request state from ip op req res)
131 | (supervisor-loop state)]
132 |
133 | ;; Worker starts processing task
134 | [#(processing ,worker ,client ,ip ,op ,req ,res)
135 | ;; Record task being processed
136 | (hashtable-set! (supervisor-state-processing-tasks state)
137 | worker
138 | `#(,client ,ip ,op ,req ,res))
139 | (supervisor-loop state)]
140 |
141 | ;; Worker completed task
142 | [#(completed ,worker ,client)
143 | ;; Clear processing record
144 | (hashtable-delete! (supervisor-state-processing-tasks state) worker)
145 | (supervisor-loop state)]
146 |
147 | ;; Worker crash - DOWN message
148 | [`(DOWN ,monitor ,worker-pid ,reason)
149 | (printf "[Supervisor] Worker crashed: ~a, reason: ~a\n" worker-pid reason)
150 | (handle-worker-crash state worker-pid reason)
151 | (supervisor-loop state)]
152 |
153 | ;; Worker exit - EXIT message
154 | [`(EXIT ,worker-pid ,reason)
155 | (unless (eq? reason 'normal)
156 | (printf "[Supervisor] Worker abnormal exit: ~a, reason: ~a\n" worker-pid reason)
157 | (handle-worker-crash state worker-pid reason))
158 | (supervisor-loop state)]
159 |
160 | ;; Get status
161 | [#(get-status ,from)
162 | (send from `#(supervisor-status ,state))
163 | (supervisor-loop state)]
164 |
165 | ;; Stop Supervisor
166 | [#(stop)
167 | (printf "[Supervisor] Stopping all workers\n")
168 | (for-each (lambda (w) (send (cdr w) #(stop)))
169 | (supervisor-state-workers state))
170 | (exit 'normal)]
171 |
172 | [,msg
173 | (printf "[Supervisor] Unknown message: ~a\n" msg)
174 | (supervisor-loop state)]))
175 |
176 | ;; Handle new request
177 | (define (handle-new-request state from ip op req res)
178 | (let ([workers (supervisor-state-workers state)])
179 | (if (null? workers)
180 | ;; No available Workers, add to queue
181 | (begin
182 | (printf "[Supervisor] No available workers, request queued\n")
183 | (supervisor-state-pending-tasks-set! state
184 | (append (supervisor-state-pending-tasks state)
185 | (list `#(,from ,ip ,op ,req ,res)))))
186 | ;; Select Worker with minimum load
187 | (let ([worker (select-worker state)])
188 | (printf "[Supervisor] Assigning request to Worker ~a\n" (car worker))
189 | (send (cdr worker) `#(http-request ,from ,ip ,op ,req ,res))))))
190 |
191 | ;; Select Worker (simple round-robin)
192 | (define (select-worker state)
193 | ;; TODO: Implement smarter load balancing
194 | (car (supervisor-state-workers state)))
195 |
196 | ;; Handle Worker crash
197 | (define (handle-worker-crash state worker-pid reason)
198 | ;; 1. Find the crashed Worker
199 | (let ([crashed-worker (assq-ref worker-pid (supervisor-state-workers state))])
200 | (when crashed-worker
201 | ;; 2. Remove from list
202 | (supervisor-state-workers-set! state
203 | (remove (lambda (w) (eq? (cdr w) worker-pid))
204 | (supervisor-state-workers state)))
205 |
206 | ;; 3. Get unfinished task
207 | (let ([unfinished-task (hashtable-ref
208 | (supervisor-state-processing-tasks state)
209 | worker-pid
210 | #f)])
211 |
212 | ;; 4. If there are unfinished tasks, reassign
213 | (when unfinished-task
214 | (match unfinished-task
215 | [#(,client ,ip ,op ,req ,res)
216 | (printf "[Supervisor] Reassigning crashed worker's task\n")
217 | ;; Add task back to the front of queue
218 | (supervisor-state-pending-tasks-set! state
219 | (cons `#(,client ,ip ,op ,req ,res)
220 | (supervisor-state-pending-tasks state)))
221 | ;; Clear processing record
222 | (hashtable-delete! (supervisor-state-processing-tasks state)
223 | worker-pid)]))
224 |
225 | ;; 5. Create new Worker replacement
226 | (let* ([worker-id (car crashed-worker)]
227 | [new-worker (http-worker worker-id self)])
228 | (printf "[Supervisor] Creating new worker ~a to replace crashed worker\n" worker-id)
229 | (monitor new-worker)
230 | (supervisor-state-workers-set! state
231 | (cons (cons worker-id new-worker)
232 | (supervisor-state-workers state)))
233 |
234 | ;; 6. Process tasks in waiting queue
235 | (process-pending-tasks state))))))
236 |
237 | ;; Process waiting queue
238 | (define (process-pending-tasks state)
239 | (let ([pending (supervisor-state-pending-tasks state)]
240 | [workers (supervisor-state-workers state)])
241 | (unless (or (null? pending) (null? workers))
242 | (let ([task (car pending)]
243 | [worker (select-worker state)])
244 | (match task
245 | [#(,from ,ip ,op ,req ,res)
246 | (printf "[Supervisor] Assigning queued task to Worker ~a\n" (car worker))
247 | (send (cdr worker) `#(http-request ,from ,ip ,op ,req ,res))
248 | (supervisor-state-pending-tasks-set! state (cdr pending))
249 | ;; Recursively process remaining tasks
250 | (process-pending-tasks state)])))))
251 |
252 | ;; ==================== Public API ====================
253 |
254 | ;; Start Supervisor
255 | (define (start-supervisor worker-count)
256 | (supervisor-process worker-count))
257 |
258 | ;; Stop Supervisor
259 | (define (stop-supervisor supervisor)
260 | (send supervisor #(stop)))
261 |
262 | ;; Get Supervisor status
263 | (define (supervisor-status supervisor)
264 | (send supervisor `#(get-status ,self))
265 | (receive
266 | [#(supervisor-status ,state) state]
267 | [(after 1000) #f]))
268 |
269 | ;; Create Worker
270 | (define (spawn-worker supervisor id)
271 | (let ([worker (http-worker id supervisor)])
272 | (monitor worker)
273 | worker))
274 |
275 | ;; Get Worker count
276 | (define (worker-count supervisor)
277 | (let ([status (supervisor-status supervisor)])
278 | (if status
279 | (length (supervisor-state-workers status))
280 | 0)))
281 |
282 | ;; ==================== OTP HTTP Server ====================
283 |
284 | ;; Start OTP-enhanced HTTP server
285 | (define (start-otp-http-server app host port worker-count)
286 | (printf "\n[OTP HTTP Server] Startup configuration:\n")
287 | (printf " Host: ~a\n" host)
288 | (printf " Port: ~a\n" port)
289 | (printf " Worker count: ~a\n" worker-count)
290 | (printf " Fault tolerance mode: Let it crash + task redistribution\n\n")
291 |
292 | ;; Start Supervisor
293 | (let ([supervisor (start-supervisor worker-count)])
294 |
295 | ;; Start TCP listener
296 | (let ([listener (listen-tcp "::" port self)])
297 | (printf "[OTP HTTP Server] Listening on ~a:~a\n" host port)
298 |
299 | ;; Accept connection loop
300 | (spawn
301 | (lambda ()
302 | (let accept-loop ()
303 | (receive
304 | [`(accept-tcp ,listener ,error ,ip ,op)
305 | (cond
306 | [error
307 | (printf "[OTP HTTP Server] Connection accept error: ~a\n" error)]
308 | [else
309 | ;; Read request and send to Supervisor
310 | (spawn
311 | (lambda ()
312 | (let ([req (read-http-request ip)]
313 | [res (make-response)])
314 | ;; Send to Supervisor for processing
315 | (send supervisor `#(http-request ,self ,ip ,op ,req ,res))
316 | ;; Wait for completion
317 | (receive
318 | [#(request-completed ,worker)
319 | (printf "[OTP HTTP Server] Request completed\n")]
320 | [(after 30000)
321 | (printf "[OTP HTTP Server] Request timeout\n")
322 | (send-error-response op 504 "Gateway Timeout")]))))])
323 | (accept-loop)])))))
324 |
325 | ;; Return Supervisor handle
326 | supervisor))
327 |
328 | ;; Helper functions
329 | (define (assq-ref key alist)
330 | (let ([pair (assq key alist)])
331 | (and pair (cdr pair))))
332 |
333 | (define (iota n)
334 | (let loop ([i 0] [result '()])
335 | (if (< i n)
336 | (loop (+ i 1) (cons i result))
337 | (reverse result))))
338 |
339 | ;; Send error response
340 | (define (send-error-response op code message)
341 | (let ([response (format "HTTP/1.1 ~a ~a\r\nContent-Type: text/plain\r\n\r\n~a"
342 | code message message)])
343 | (guard (e [else (void)])
344 | (put-bytevector op (string->utf8 response))
345 | (flush-output-port op)
346 | (close-port op))))
347 |
348 | ) ;; end of library
--------------------------------------------------------------------------------
/test/test-igropyr.sc:
--------------------------------------------------------------------------------
1 | ;; Igropyr Http Server Test - Enhanced Version
2 | ;; Implementing Erlang OTP-style fault tolerance
3 |
4 | ;; Load ChezErlang shared library
5 | (define lib (load-shared-object "libosi.dylib"))
6 |
7 | (import (chezscheme))
8 |
9 | ;; Import ChezErlang libraries
10 | (import (chezerlang compat))
11 | (import (chezerlang internal))
12 | (import (chezerlang meta))
13 | (import (chezerlang osi))
14 | (import (chezerlang erlang))
15 | (import (chezerlang io))
16 | (import (chezerlang gen-server))
17 | (import (chezerlang supervisor))
18 | (import (chezerlang app))
19 | (import (chezerlang app-io))
20 | (import (chezerlang http))
21 | (import (chezerlang html))
22 |
23 | ;; Import Igropyr Http Server library
24 | (import (igropyr http))
25 |
26 | ;; Define current-time-ms function (compatibility)
27 | (define current-time-ms
28 | (if (top-level-bound? 'current-time-ms)
29 | current-time-ms
30 | (lambda ()
31 | (let ([t (current-time)])
32 | (+ (* (time-second t) 1000)
33 | (quotient (time-nanosecond t) 1000000))))))
34 |
35 | ;; ==================== Simplified Enhanced Features (Using Existing API) ====================
36 |
37 | ;; Error counter
38 | (define error-count (make-parameter 0))
39 | (define request-count (make-parameter 0))
40 |
41 | ;; Enhanced error handling middleware
42 | (define (enhanced-error-middleware)
43 | (lambda (req res next)
44 | (guard (e [else
45 | (error-count (+ (error-count) 1))
46 | (printf "[ERROR] ~a (Total: ~a)\n" e (error-count))
47 | (set-status! res 500)
48 | (send-html! res
49 | (format "
50 |
51 | 500 - Server Error
52 | Error: ~a
53 | Server will recover automatically
54 |
55 | " e))])
56 | (next))))
57 |
58 | ;; Request counter middleware
59 | (define (request-counter-middleware)
60 | (lambda (req res next)
61 | (request-count (+ (request-count) 1))
62 | (when (= (modulo (request-count) 100) 0)
63 | (printf "[STATS] Processed ~a requests, ~a errors\n"
64 | (request-count) (error-count)))
65 | (next)))
66 |
67 | ;; Timeout protection middleware
68 | (define (timeout-middleware timeout-ms)
69 | (lambda (req res next)
70 | (let ([start-time (current-time-ms)])
71 | (dynamic-wind
72 | (lambda () (void))
73 | (lambda () (next))
74 | (lambda ()
75 | (let ([elapsed (- (current-time-ms) start-time)])
76 | (when (> elapsed timeout-ms)
77 | (printf "[TIMEOUT] Request processing time: ~a ms\n" elapsed))))))))
78 |
79 | ;; Health check route
80 | (define (add-health-check-route app)
81 | (app-get app "/health"
82 | (lambda (req res)
83 | (let ([health-score (if (> (request-count) 0)
84 | (- 100 (* 100.0 (/ (error-count) (request-count))))
85 | 100)])
86 | (send-json! res
87 | (format "{
88 | \"status\": \"~a\",
89 | \"requests\": ~a,
90 | \"errors\": ~a,
91 | \"health_score\": ~a,
92 | \"timestamp\": \"~a\"
93 | }"
94 | (if (>= health-score 90) "healthy" "degraded")
95 | (request-count)
96 | (error-count)
97 | (round health-score)
98 | (current-date)))))))
99 |
100 | ;; Simulate fault tolerance routes
101 | (define (add-fault-tolerance-demo-routes app)
102 | ;; Simulate random crashes
103 | (app-get app "/test/random-crash"
104 | (lambda (req res)
105 | (if (= (random 3) 0)
106 | (error 'simulated-crash "Simulated random crash")
107 | (send-json! res "{\"status\": \"success\"}"))))
108 |
109 | ;; Simulate CPU intensive operation
110 | (app-get app "/test/cpu-intensive"
111 | (lambda (req res)
112 | (let ([result (let loop ([n 1000000] [sum 0])
113 | (if (= n 0)
114 | sum
115 | (loop (- n 1) (+ sum n))))])
116 | (send-json! res
117 | (format "{\"result\": ~a}" result)))))
118 |
119 | ;; Simulate slow request
120 | (app-get app "/test/slow"
121 | (lambda (req res)
122 | (simple-sleep 2) ; 2 second delay
123 | (send-json! res "{\"status\": \"slow request completed\"}"))))
124 |
125 | (printf "\n====================================\n")
126 | (printf " Igropyr Http Server Test (Enhanced)\n")
127 | (printf " With OTP Fault Tolerance\n")
128 | (printf "====================================\n\n")
129 |
130 | ;; Create application
131 | (define app (create-app))
132 | (printf "✅ Created Igropyr Http Server application\n")
133 |
134 | ;; Configure application
135 | (app-configure app "port" 8080)
136 | (app-configure app "host" "0.0.0.0")
137 | (app-configure app "base-dir" (current-directory))
138 | (printf "✅ Application configuration complete\n")
139 |
140 | ;; Add logger middleware
141 | (app-use app (logger-middleware))
142 | (printf "✅ Added logger middleware\n")
143 |
144 | ;; Add CORS middleware
145 | (app-use app (cors-middleware))
146 | (printf "✅ Added CORS middleware\n")
147 |
148 | ;; Add error handler middleware
149 | (app-use app (error-handler-middleware))
150 | (printf "✅ Added error handler middleware\n")
151 |
152 | ;; Simple sleep function (using built-in sleep)
153 | (define (simple-sleep seconds)
154 | (let ([end-time (+ (time-second (current-date)) seconds)])
155 | (let loop ()
156 | (when (< (time-second (current-date)) end-time)
157 | (let () (void))))))
158 |
159 | ;; Set static file directory
160 | (unless (file-exists? "static")
161 | (mkdir "static"))
162 | (app-static app "/static" "static")
163 | (printf "✅ Set static file directory: /static -> ./static\n")
164 |
165 |
166 | ;; Create simple test script
167 | (call-with-output-file "test-fault-tolerance.sh"
168 | (lambda (p)
169 | (display "#!/bin/bash\n" p)
170 | (display "echo 'Fault Tolerance Test'\n" p)
171 | (display "echo '===================='\n" p)
172 | (display "echo ''\n" p)
173 | (display "echo 'Test 1: Normal Request'\n" p)
174 | (display "curl http://localhost:3005/\n" p)
175 | (display "echo ''\n" p)
176 | (display "echo ''\n" p)
177 | (display "echo 'Test 2: Health Check'\n" p)
178 | (display "curl http://localhost:3005/health\n" p)
179 | (display "echo ''\n" p)
180 | (display "echo ''\n" p)
181 | (display "echo 'Test 3: Random Crash Test (10 times)'\n" p)
182 | (display "for i in {1..10}; do\n" p)
183 | (display " echo \" Request $i:\"\n" p)
184 | (display " curl -s http://localhost:3005/test/random-crash || echo 'Crashed but service continues'\n" p)
185 | (display " echo ''\n" p)
186 | (display "done\n" p)
187 | (display "echo ''\n" p)
188 | (display "echo 'Test 4: Check Health Status Again'\n" p)
189 | (display "curl http://localhost:3005/health\n" p)
190 | (display "echo ''\n" p))
191 | 'replace)
192 | (system "chmod +x test-fault-tolerance.sh")
193 | (printf "✅ Created fault tolerance test script: test-fault-tolerance.sh\n")
194 |
195 | ;; Set dynamic page directory
196 | (unless (file-exists? "dynamic")
197 | (mkdir "dynamic"))
198 | (app-dynamic app "/dynamic" "dynamic")
199 | (printf "✅ Set dynamic page directory: /dynamic -> ./dynamic\n")
200 |
201 | ;; Home page route - serve static index.html
202 | (app-get app "/"
203 | (lambda (req res)
204 | ;; Read and serve the static index.html file
205 | (let ([content (call-with-input-file "static/index.html"
206 | (lambda (p)
207 | (get-string-all p)))])
208 | (send-html! res content))))
209 |
210 | ;; API Info Route - System Status
211 | (app-get app "/api/info"
212 | (lambda (req res)
213 | (send-json! res
214 | (format "{
215 | \"status\": \"running\",
216 | \"port\": 8080,
217 | \"host\": \"0.0.0.0\",
218 | \"server\": \"Igropyr/1.0\",
219 | \"uptime\": \"~a\"
220 | }"
221 | (current-date)))))
222 |
223 |
224 | ;; User details route (with params)
225 | (app-get app "/api/user/:id"
226 | (lambda (req res)
227 | (let ([user-id (assoc "id" (request-params req))])
228 | (if user-id
229 | (send-json! res
230 | (format "{\"id\": \"~a\", \"name\": \"User ~a\", \"details\": \"This is user ~a's profile\"}"
231 | (cdr user-id) (cdr user-id) (cdr user-id)))
232 | (begin
233 | (set-status! res 404)
234 | (send-json! res "{\"error\": \"User not found\"}"))))))
235 |
236 | ;; JSON test route
237 | (app-get app "/test/json"
238 | (lambda (req res)
239 | (send-json res "{\"test\": \"success\", \"timestamp\": \"2024-01-01\"}")))
240 |
241 | ;; Redirect test
242 | (app-get app "/test/redirect"
243 | (lambda (req res)
244 | (redirect res "/")))
245 |
246 |
247 | ;; Dynamic rendering test - read test.html and inject dynamic content
248 | (app-get app "/test/dynamic"
249 | (lambda (req res)
250 | ;; Read the static test.html template
251 | (let* ([template (call-with-input-file "static/test.html"
252 | (lambda (p)
253 | (get-string-all p)))]
254 | ;; Get current time string
255 | [time-str (format "~a" (current-date))]
256 | ;; Replace placeholder with dynamic content
257 | [dynamic-content (string-append
258 | "Dynamic Content:
"
259 | ""
260 | "- Request time: " time-str "
"
261 | "- Request method: " (request-method req) "
"
262 | "- Request path: " (request-path req) "
"
263 | "- Server timestamp: " (number->string (current-time-ms)) " ms
"
264 | "- Random number: " (number->string (random 1000)) "
"
265 | "
")]
266 | ;; Insert dynamic content before closing body tag
267 | [html (string-append
268 | (substring template 0 (- (string-length template) 14)) ; Remove