|
| 1 | +#lang racket |
| 2 | + |
| 3 | +;; the skeleton for this server is taken from |
| 4 | +;; https://github.com/CHARM-BDF/mediKanren-MCP |
| 5 | + |
| 6 | +(define port |
| 7 | + (let ((port (getenv "ANSWERKG_MEDIKANREN_PORT"))) |
| 8 | + (if port |
| 9 | + (string->number port) |
| 10 | + 8181))) |
| 11 | + |
| 12 | +(define max-worker-count |
| 13 | + (let ((count (getenv "MAX_WORKER_COUNT"))) |
| 14 | + (if count |
| 15 | + (string->number count) |
| 16 | + 5))) |
| 17 | + |
| 18 | +(define worker-semaphore (make-semaphore max-worker-count)) |
| 19 | + |
| 20 | +(require web-server/servlet) |
| 21 | +(require web-server/servlet-env) |
| 22 | +(require web-server/http/bindings) |
| 23 | +(require web-server/http/json) |
| 24 | +(require json) |
| 25 | +(require "answerkg.rkt") |
| 26 | + |
| 27 | +(require racket/engine) |
| 28 | +(define (job-failure x) |
| 29 | + (response/xexpr x)) |
| 30 | + |
| 31 | +(define timeout (* 10 1000)) ;; in miliseconds |
| 32 | +(define max-waiting (+ timeout 500)) |
| 33 | + |
| 34 | +(define (thunk-with-timeout thunk) |
| 35 | + (lambda () |
| 36 | + (define e (engine (lambda (x) (thunk)))) |
| 37 | + (if (engine-run timeout e) |
| 38 | + (engine-result e) |
| 39 | + (begin |
| 40 | + (displayln "timeout") |
| 41 | + (job-failure "timeout"))))) |
| 42 | + |
| 43 | +(define (work-safely work) |
| 44 | + (define custodian.work (make-custodian)) |
| 45 | + (define result |
| 46 | + ;; current-custodian will collect all file handles opened during work |
| 47 | + (parameterize ((current-custodian custodian.work)) |
| 48 | + (with-handlers ((exn:fail? |
| 49 | + (lambda (v) |
| 50 | + ((error-display-handler) (exn-message v) v) |
| 51 | + (job-failure (exn-message v)))) |
| 52 | + ((lambda _ #t) |
| 53 | + (lambda (v) |
| 54 | + (define message |
| 55 | + (string-append "unknown error: " |
| 56 | + (with-output-to-string (thunk (write v))))) |
| 57 | + (pretty-write message) |
| 58 | + (job-failure message)))) |
| 59 | + (call-in-nested-thread work custodian.work)))) |
| 60 | + (custodian-shutdown-all custodian.work) ; close all file handles opened during work |
| 61 | + result) |
| 62 | +(define (handle-work-safely handle-request) |
| 63 | + (lambda (request) |
| 64 | + (call-with-semaphore |
| 65 | + worker-semaphore |
| 66 | + (thunk-with-timeout |
| 67 | + (lambda () |
| 68 | + (work-safely (lambda () (handle-request request)))))))) |
| 69 | +(define w handle-work-safely) |
| 70 | + |
| 71 | +(define (start request) |
| 72 | + (server-dispatch request)) |
| 73 | + |
| 74 | +(define-values (server-dispatch server-url) |
| 75 | + (dispatch-rules |
| 76 | + (("") (w handle-index-request)) |
| 77 | + (("query") (w handle-query-request)) |
| 78 | + (else (w handle-index-request)))) |
| 79 | + |
| 80 | +(define (handle-index-request request) |
| 81 | + (response/xexpr |
| 82 | + `(html (body |
| 83 | + (div |
| 84 | + (h1 "Welcome to the answerkg FLT1 Racket server!") |
| 85 | + (p " The endpoint is:" |
| 86 | + (ul |
| 87 | + (li "query?subject=...&predicate=...&object=... " (a ([href "/query?subject=&predicate=&object=NCBIGene:4627"]) "(example)"))))) |
| 88 | +)))) |
| 89 | + |
| 90 | +(define (handle-query-request request) |
| 91 | + (define params (request-bindings request)) |
| 92 | + (response/jsexpr |
| 93 | + (query |
| 94 | + (maybe-cdr (assoc 'subject params)) (maybe-cdr (assoc 'predicate params)) (maybe-cdr (assoc 'object params))))) |
| 95 | + |
| 96 | +(define (maybe-cdr x) |
| 97 | + (if x (cdr x) "")) |
| 98 | + |
| 99 | +(define (send-ready-signal) |
| 100 | + ;; Sends signal to `pm2` when the server is ready |
| 101 | + (system "kill -s SIGUSR2 $PM2_PID")) |
| 102 | + |
| 103 | +(define (serve) |
| 104 | + (serve/servlet start |
| 105 | + #:servlet-path "" |
| 106 | + #:port port |
| 107 | + #:servlet-regexp #rx"" |
| 108 | + #:max-waiting max-waiting |
| 109 | + #:launch-browser? #false) |
| 110 | + (send-ready-signal)) |
| 111 | +(serve) |
0 commit comments