diff options
| author | Jakob L. Kreuze | 2026-07-11 12:50:07 -0400 |
|---|---|---|
| committer | Jakob L. Kreuze | 2026-07-11 15:12:48 -0400 |
| commit | 31c871da155e2c7ef5e1d825b40702d6ba1c9545 (patch) | |
| tree | 44e8a8eb2b9d57071c175c4ff963a16b504f6e54 /weeb | |
| parent | 852eb83cd9e78a8428d4a1175ed162b0e92e4681 (diff) | |
Primordial router
Diffstat (limited to 'weeb')
| -rw-r--r-- | weeb/main.scm | 27 | ||||
| -rw-r--r-- | weeb/request.scm | 24 | ||||
| -rw-r--r-- | weeb/router.scm | 48 |
3 files changed, 91 insertions, 8 deletions
diff --git a/weeb/main.scm b/weeb/main.scm index 2fc6df1..4c7e69f 100644 --- a/weeb/main.scm +++ b/weeb/main.scm @@ -20,16 +20,18 @@ #:use-module (ice-9 match) #:use-module (ice-9 regex) #:use-module (srfi srfi-1) + #:use-module (srfi srfi-11) ;; Temporary, probably #:use-module (weeb fcgi) #:use-module (weeb request) + #:use-module (weeb router) #:use-module (weeb struct) #:export (weeb main)) (define (receive-request) (define string->method (match-lambda - (("GET" 'GET) - ("POST" 'POST)))) + ("GET" 'GET) + ("POST" 'POST))) ;; According to RFC 9110, header names must be treated as case-insensitive. (define headers (filter-map @@ -44,13 +46,26 @@ ,(match:substring match-object 2)) #f))) (environ))) - (let* ((method (getenv "REQUEST_METHOD")) + (let* ((method (string->method (getenv "REQUEST_METHOD"))) (uri (getenv "REQUEST_URI"))) (make-struct <request> method uri '() headers (fcgi-read)))) +(define (show-headers request) + ;; TODO: Return a response object. + (with-accessors (request headers) + (fcgi-display "Content-type: text/html\r\n\r\n") + (fcgi-display (format #f "~a" headers)))) + +(define (show-message request message) + (fcgi-display (format #f "~a" message))) + +(define-router router + ("/headers" (GET) show-headers) + ("/echo/:message:" (GET) show-message)) + (define (run-server) (while (zero? (fcgi-accept)) (let ((request (receive-request))) - (with-accessors (request method uri headers body) - (fcgi-display "Content-type: text/html\r\n\r\n") - (fcgi-display (format #f "~a" headers)))))) + (iter-routes request router)))) + +(run-server) diff --git a/weeb/request.scm b/weeb/request.scm new file mode 100644 index 0000000..42cd933 --- /dev/null +++ b/weeb/request.scm @@ -0,0 +1,24 @@ +;;; Weeb --- Web microframework for GNU Guile +;;; Copyright © 2026 Jakob Kreuze <zerodaysfordays@sdf.org> +;;; +;;; This file is part of Weeb. +;;; +;;; Weeb is free software: you can redistribute it and/or modify it +;;; under the terms of the GNU Affero General Public License as +;;; published by the Free Software Foundation, either version 3 of the +;;; License, or (at your option) any later version. +;;; +;;; Weeb is distributed in the hope that it will be useful, but WITHOUT +;;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +;;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General +;;; Public License for more details. +;;; +;;; You should have received a copy of the GNU Affero General Public +;;; License along with Weeb. If not, see <https://www.gnu.org/licenses/>. + +(define-module (weeb request) + #:use-module (weeb struct) + #:export (<request>)) + +(define-struct <request> + method uri parameters headers body) diff --git a/weeb/router.scm b/weeb/router.scm index 8c4b502..575860b 100644 --- a/weeb/router.scm +++ b/weeb/router.scm @@ -17,9 +17,12 @@ ;;; License along with Weeb. If not, see <https://www.gnu.org/licenses/>. (define-module (weeb router) + #:use-module (ice-9 match) #:use-module (ice-9 regex) + #:use-module (srfi srfi-11) #:use-module (weeb struct) - #:export (define-router)) + #:export (define-router + iter-routes)) (define (uri-template->regexp-string template) ;; TODO: Assert that the first character is a slash. @@ -30,9 +33,50 @@ (if variable-match (list (string->symbol (match:substring variable-match 1)) "([^/]+)") (list #f component)))) - (string-split (if trailing-slash? + (string-split (if (and trailing-slash? (not (string= template "/"))) (substring template 1 (- (string-length template) 1)) (substring template 1)) #\/))) (values (string-append "/" (string-join (map cadr components) "/")) (filter symbol? (map car components)))) + +(define (handle-route request route-object) + (define (ladder-parameters match-object n) + (map (lambda (n) (match:substring match-object (+ 1 n))) + (iota n))) + (with-accessors (request method uri) + (match route-object + ((route-regexp route-variables route-methods proc) + (let ((match-object (regexp-exec route-regexp uri))) + (if (and (memq method route-methods) match-object) + ;; Unfortunately can't reflect on procedure names. We *could*, I + ;; suppose, pass them as keyword arguments, but I think I want to + ;; rethink this interface in the future. + (apply proc request (ladder-parameters match-object (length route-variables))) + #f)))))) + +(define-syntax %handle-route + (lambda (stx) + (syntax-case stx () + ((_ (template methods proc)) + #'(let-values (((regexp-string variables) (uri-template->regexp-string template))) + (let ((regexp (make-regexp regexp-string))) + (list regexp variables (quote methods) proc))))))) + +(define-syntax define-router + (lambda (stx) + (syntax-case stx () + ((_ name route* ...) + #'(define name (list (%handle-route route*) ...)))))) + +(use-modules (weeb fcgi)) +(define (default-handler request) + (fcgi-display "Status: 404 Not Found\r\n") + (fcgi-display "Content-type: text/html\r\n\r\n") + (fcgi-display "Route not found.\n")) + +(define (iter-routes request router) + (match router + (() (default-handler request)) + ((route routes ...) + (or (handle-route request route) (iter-routes request routes))))) |