X-Git-Url: https://thelambdalab.xyz/gitweb/index.cgi?p=scratchy.git;a=blobdiff_plain;f=gopher-server.scm;fp=gopher-server.scm;h=f047bdc18298f21125010990958f7951d9d49bed;hp=0000000000000000000000000000000000000000;hb=fe1ccf11bcd8dfaab0dde02e7f16378f509447c8;hpb=0ee2c323a8d80343a8110928007c596c66c8084b diff --git a/gopher-server.scm b/gopher-server.scm new file mode 100644 index 0000000..f047bdc --- /dev/null +++ b/gopher-server.scm @@ -0,0 +1,166 @@ +(import (chicken tcp) + (chicken port) + (chicken io) + (chicken string) + (chicken pathname) + (chicken file posix) + (chicken time posix) + (chicken condition) + (chicken process-context) + srfi-13) + +(define gopher-index-file-name "index") + +;;; Server loop +;; We don't actually use worker threads here to handle requests, +;; the server just blocks until the first request is finished. + +(define (run-server gopher-root gopher-server-hostname gopher-server-port) + (print "Gopher server listening on port " gopher-server-port " ...") + (let ((listener (tcp-listen gopher-server-port))) + (let server-loop () + (let-values (((in-port out-port) (tcp-accept listener))) + (let* ((line (read-line in-port)) + (selector (string-trim-both line))) + (let-values (((local-ip remote-ip) (tcp-addresses in-port))) + (print "Accepted connection from " remote-ip + " on " (seconds->string))) + (condition-case + (begin + (with-output-to-port out-port + (lambda () + (serve-file + (if (= (string-length selector) 0) + "1/" + selector) + gopher-root + gopher-server-hostname + gopher-server-port))) + (print "... served selector '" selector "'. Closing connection.")) + (o (exn) + (print-error-message o out-port) + (print-error-message o) + (print "Error while attempting to serve selector " selector ".")))) + (close-input-port in-port) + (close-output-port out-port)) + (server-loop)) + (tcp-close listener))) + + +;;; Item retrieval + +(define (serve-file selector gopher-root server-host server-port) + (let ((type (with-input-from-string (substring selector 0 1) read)) + (path (substring selector 1))) + (case type + ((1) (serve-index-file path gopher-root server-host server-port)) + ((0) (serve-text-file path gopher-root)) + ((9 g I) (serve-binary-file path gopher-root)) + (else (error "Unhandled file type:" type))))) + +(define (serve-index-file path gopher-root server-host server-port) + (let ((file-name (make-pathname (list gopher-root path) gopher-index-file-name))) + (if (regular-file? file-name) + (with-input-from-file file-name + (lambda () + (render-index (read) + path + server-host + server-port))) + (error "Index file not found.")))) + +(define (serve-text-file path gopher-root) + (let ((file-name (make-pathname gopher-root path))) + (if (regular-file? file-name) + (with-input-from-file file-name + (lambda () + (for-each + (lambda (line) + (print line "\r")) + (read-lines)))) + (error "File not found." file-name)))) + +(define (serve-binary-file path gopher-root) + (let ((file-name (make-pathname gopher-root path))) + (if (regular-file? file-name) + (with-input-from-file file-name + (lambda () + (let loop ((b (read-byte))) + (if (eof-object? b) + 'done + (begin + (write-byte b) + (loop (read-byte))))))) + (error "File not found." file-name)))) + + +;;; Index rendering + +(define entry-type car) + +(define entry-name cadr) + +(define (has-selector? entry) (>= (length entry) 3)) + +(define (entry-selector entry) (list-ref entry 2)) + +(define (has-host? entry) (>= (length entry) 4)) + +(define (entry-host entry) (list-ref entry 3)) + +(define (has-port? entry) (>= (length entry) 5)) + +(define (entry-port entry) (list-ref entry 4)) + +(define (render-entry entry default-selector default-host default-port selector-prefix) + (let ((name-string (entry-name entry))) + (for-each + (lambda (name-string-line) + (print* (entry-type entry) name-string-line) + (print* "\t" (if (has-selector? entry) + (if (has-host? entry) + (entry-selector entry) + (conc (entry-type entry) + (make-pathname selector-prefix (entry-selector entry)))) + default-selector)) + (print* "\t" (if (has-host? entry) + (entry-host entry) + default-host)) + (print* "\t" (if (has-port? entry) + (entry-port entry) + default-port)) + (print* "\r\n")) + (string-split name-string "\n" #t)))) + +(define (render-index index selector-prefix this-host this-port) + (for-each + (lambda (entry) + (if (eq? (entry-type entry) 'i) + (render-entry entry "fake.selector" "fake.host" 1 selector-prefix) + (render-entry entry "" this-host this-port selector-prefix))) + index) + (print ".")) + + +;;; main + +(define (main) + (let ((progname (car (argv))) + (args (cdr (argv)))) + (if (or (< (length args) 2) + (equal? (car args) "-h") + (equal? (car args) "--help")) + (print "Usage:\n" + progname " -h/--help\n" + progname " gopher-root-dir server-hostname server-port") + (let ((root (car args)) + (hostname (cadr args)) + (port (if (= (length args) 3) (string->number (caddr args)) 70))) + (if port + (run-server root hostname port) + (error "Invalid port argument." port)))))) + +(main) + +;; (define (test) + ;; (run-server "gopher-root" "localhost" 70))