Guile web server implementacija igre stavkov

Table of Contents

1. guile web server implementacija

Uporabilo bomo guile web server. Začnemo z vključitvijo modula za spletni strežnik.

Beremo dva dokumenta iz uradne dokumentacije: https://www.gnu.org/software/guile/manual/html_node/Web-Server.html https://www.gnu.org/software/guile/manual/html_node/Web-Examples.html

#!/usr/bin/env guile !#
(use-modules (web server))

(define (igra-stavkov-handler request request-body)
  (values '((content-type . (text/plain)))
  "IGRA STAVKOV SE PRIČENJA. OHOHO"))

(run-server igra-stavkov-handler)

1.1. navodila

Dodamo html file z navodili:

   <html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>igra stavkov</title>
</head>
<body>
  <h1>igra stavkov</h1>
  <h2>v živo</h2>
  <p>
    Vsak igralec potrebuje listek in pisalo. Igralci skupaj odgovarjajo na vprašanja, po vsakem odgovoru pa listek prepognejo in podajo naprej. Svoj naslednji odgovor nato napišejo pod prepognjen del in spet podajo listek naprej. Ko odogovorijo na vsa vprašanja, še enkrat podajo listek naprej naslednjemu igralcu in preberejo cel stavek.
  </p>

  <h2>prek spleta (naša varianta)</h2>
  <p>
    Vsak igralec potrebuje računalnik z brskalnikom in internetno povezavo. Dobavna veriga obsega rudnike redkih kovin, silicij, proizvodnjo čipov, komunikacijsko mrežo, etcetera.
  </p>

  <p>
  Na naši aplikaciji je igra zelo podobna. Izberete število igralcev in ustvarite novo sobo za igranje. Povezavo do sobo pošljete soigralcem in po vrsti odgovarjate na vprašanja, dokler ne izpolnite vseh. Vsakemu igralcu se na koncu izpiše en sestavljen stavek.
</p>

  <form method="POST" action="/soba/ustvari">
    <label for="num-of-players">Izberi število igralcev</label>
    <input type="number" min="1" name="num-of-players" id="num-of-players"></input>
    <input type="submit" value="igraj">
  </form>
</body>
    </html>

Potem prilagodimo handler, da vrne html dokument, ki je zgoraj definiran.

#!/usr/bin/env guile !#
(use-modules (web server)
             (ice-9 binary-ports))

;; tukajo preberemo index.html in ga prikazemo
(define (igra-stavkov-handler request request-body)
  (values '((content-type . (text/html)))
    (call-with-input-file "index.html" get-bytevector-all)))


(display "strežnik igre stavkov teče: http://localhost:8080")
(run-server igra-stavkov-handler)

1.2. procesiranje obrazca

Obrazec (form) preberemo iz request objekta, vrednost num-of-players:

Za branje POST requesta smo na netu našli snippet kode, ki pretvori zahtevek (bytevector seznam). Kodo smo kar spravili v web-helpers.scm.

Dodali smo tudi funkcijo request-url, ki vrne niz poti (URL) nekega zahtevka. Zakaj moramo to početi? Ker smo na nekoliko nižjem nivoju. Zahtevek (reqest) je razdeljen na metapodatke in vsebino, URI pa je eden izmed metapodatkov, ki jih pridobimo (s funkcijo request-uri).

(define-module (vaja nova-igra-stavkov web-helpers)
  #:use-module (web request)
  #:use-module (web uri)
  #:use-module (ice-9 match)
  #:use-module (rnrs bytevectors)
  #:use-module (srfi srfi-1)
  #:use-module (srfi srfi-26))

(define-public (request-url request)
  "Vrne pot zahtevka kot niz"
  (uri->string (request-uri request)))

;; Dekodiranje request bodyja v alist
;; Vir: https://stackoverflow.com/questions/46656074/how-to-read-post-data-in-the-guile-web-server
(define (acons-list k v alist)
  "Add V to K to alist as list"
  (let ((value (assoc-ref alist k)))
    (if value
        (let ((alist (alist-delete k alist)))
          (acons k (cons v value) alist))
        (acons k (list v) alist))))

(define (list->alist lst)
  "Build a alist of list based on a list of key and values.
  Multiple values can be associated with the same key"
  (let next ((lst lst)
             (out '()))
    (if (null? lst)
        out
        (next (cdr lst) (acons-list (caar lst) (cdar lst) out)))))

(define-public (decode-body bv)
  "Convert BV querystring or form data to an alist"
  (define string (utf8->string bv))
  (define pairs (map (lambda (str) (string-split str #\=))
                     ;; semi-colon and amp can be used as pair separator
                     (append-map (lambda (str) (string-split str #\;))
                                 (string-split string #\&))))
  (list->alist (map (match-lambda
                      ((key value)
                       (cons (uri-decode key) (uri-decode value)))) pairs)))

Na tej točki smo se odločili presedlati iz poganjanja fajlov iz terminala v uporabo geiser. Zato je bilo treba popraviti imenske prostore modulov. Guile išče module po load-path-u. Geiser doda root projekta v load path, kar pomeni da moramo pred fajle dodati vaja nova-igra-stavkov.

Problem, na katerega smo naleteli, je da če program požene web server, REPL ni več odziven. Tudi če fajl poženemo lahko samo vidimo output strežnika, ne pa tudi uporabljati REPL. Rešitev problema smo našli na naslovu: https://systemreboot.net/post/live-hacking-a-guile-web-server

Preden poženemo spletni strežnik, je odpremo nov REPL, v katerega se poveže geiser.

(define-module (vaja nova-igra-stavkov app3)
  #:use-module (vaja nova-igra-stavkov web-helpers)
  #:use-module (web server)
  #:use-module (web request)
  #:use-module (web response)
  #:use-module (ice-9 binary-ports)
  #:use-module ((system repl server) #:prefix repl:))

(define (not-found request)
  "Vrne stran za 404"
  (values (build-response #:code 404)
          (string-append "Stran ni najdena: " (request-url request))))

(define (ustvari-novo-sobo request-body)
  "Ustvari novo sobo"
  (let* ((vrednosti (decode-body request-body))
         (st-igralcev (car (assoc-ref vrednosti "num-of-players"))))
    (display vrednosti)

    (values '((content-type . (text/html)))
      (string-append "<h1>Ustvarjena je nova soba za " st-igralcev " igralcev</h1>"))))

(define (igra-stavkov-handler request request-body)
  (let ((pot (request-url request)))
    (cond ((equal? pot "/")
           (values '((content-type . (text/html)))
                   (call-with-input-file "index.html" get-bytevector-all)))
          ((equal? pot "/soba/ustvari")
           (ustvari-novo-sobo request-body))

          (else (not-found request)))))

;; poženi REPL!
(repl:spawn-server
  (repl:make-tcp-server-socket))

;; poženi web server
(display "strežnik igre stavkov teče:\n\nhttp://localhost:8080\n---\n")
;; Klic handlerja spremenimo, da ga je mogoče dinamično nalagat preko REPL
(run-server (lambda (request body)
              ((module-ref (current-module)
                           'igra-stavkov-handler)
               request body)))

Dodana je nova skripta, app3.scm, ki jo poženeš, nato pa se povežeš v novonastali repl, z ukazom: geiser-connect (ENT ENT ENT)

Author: Yuri

Created: 2025-02-13 čet 21:47

Validate