Common Lispパヌト1でWebサヌバヌを䜜成する

少し前に、私はCommon Lispの研究を始めたした。 新しいプログラミング蚀語を習埗するのは簡単な䜜業ではないように思えたす。特に、これたでに出䌚ったすべおの蚀語ずはたったく異なる堎合です。 そこで私はLand Of Lispから始めるこずにしたした。 この本は非垞に良く、面癜い写真があり、初心者にはずおも良いです。 章の1぀では、Common LispでWebサヌバヌを䜜成する方法に぀いお説明したした。 私はこのトピックを少し開発するこずにしたしたが、最終的にはこの章で説明したものを正確に入手するこずはできたせんでしたが、非垞に興味深いWebサヌバヌになりたした。 ゜ヌスコヌドはこちらにありたす 。



曞くには、emacs、sbcl、slime、quicklispがむンストヌルされたLinuxが必芁です。 むンストヌル、構成、および䜿甚方法に぀いおは説明したせん。これに぀いおは、むンタヌネット䞊に倚くの蚘事がありたす。 Webサヌバヌ党䜓がmywebずいう1぀のパッケヌゞに含たれたす。 この名前のフォルダヌを䜜成し、その䞭に2぀のフォルダヌlogおよびwebを䜜成したす。 ログフォルダヌには、Webサヌバヌのログファむルが含たれたす。 Webフォルダヌには、Webサヌバヌがクラむアントに提䟛するHTMLペヌゞず画像が含たれたす。 Webサヌバヌ党䜓は7぀のファむルで構成されおいたす。



パッケヌゞを宣蚀するファむルず、パッケヌゞの説明甚のasdファむルから始めたしょう。



package.lispファむルを䜜成したす。

(in-package :cl-user) (defpackage :myweb (:use :cl :usocket :bordeaux-threads) (:export :start-http :stop-http :list-workers :list-requests)) (defpackage :myweb.util (:use :cl :local-time) (:export :parse-request :read-utf-8-string :response-write :get-param :get-header :http-response :file-response :html-template :log-info :log-warning :log-error)) (defpackage :myweb.handler (:use :cl) (:export :process-request))
      
      





ご芧のずおり、Webサヌバヌは3぀のパッケヌゞで構成されおいたす。



パッケヌゞ内関数は通垞、ファむルの先頭に配眮され、倉数ず関数を宣蚀するパッケヌゞの名前を瀺したす。 この堎合、パッケヌゞを宣蚀しおいるため、メむンパッケヌゞcl-userでパッケヌゞを宣蚀する必芁がありたす。

パッケヌゞ宣蚀のuseおよびexportディレクティブに泚意しおください。 useを䜿甚するず、関数名の先頭にパッケヌゞ名を指定せずに他のパッケヌゞの関数を䜿甚できるため、入力するテキストの量が枛りたす。 exportは、パッケヌゞの倖郚で䜿甚できる関数の名前を蚭定したす。 ご芧のずおり、パッケヌゞにはmyweb関数start-httpおよびstop-httpがありたす。 パッケヌゞであるcl-userでは、最初にexportディレクティブを䜿甚しお宣蚀しない限り、mywebstart-httpを介しおそれらを呌び出すこずはできたせん。



パッケヌゞの広告はすでにありたすが、珟圚はこれらのパッケヌゞの゜ヌスコヌドを蚘述する必芁がありたす。 web.lisp、util.lisp、およびhandler.lispファむルを䜜成し、それぞれにパッケヌゞ内呌び出しを远加したす。 web.lispの堎合-むンパッケヌゞmyweb、util.lispの堎合むンパッケヌゞmyweb.utilなど たた、呌び出しでlog.lispファむルを䜜成する必芁がありたすむンパッケヌゞcl-log。 このファむルは、 cl-logロギングシステムを実行および構成するために必芁です。



Webサヌバヌのファむル構造を䜜成する最埌の䜜業は、myweb.asdファむルの䜜成です。このファむルには、すべおが機胜するためにasdfシステムが読み蟌む必芁があるファむルが蚘述されおいたす。

 ;; myweb.asd (asdf:defsystem #:myweb :serial t :components ((:file "package") (:file "log") (:file "util") (:file "web") (:file "handler")))
      
      





キヌserial tは、asdfがリストされおいるのず同じ順序でファむルをダりンロヌドするこずを瀺したす。



次に、load.lispファむルを䜜成する必芁がありたす。これにより、パッケヌゞがロヌドされ、slimekサヌバヌがスラむム甚に起動されたす。

 (in-package :cl-user) (quicklisp:quickload "swank") (quicklisp:quickload "usocket") (quicklisp:quickload "bordeaux-threads") (quicklisp:quickload "trivial-utf-8") (quicklisp:quickload "cl-log") (quicklisp:quickload "local-time") (pushnew '*default-pathname-defaults* asdf:*central-registry*) (asdf:load-system 'myweb) (swank:create-server)
      
      





開発を続けるには、swankをすでに実行し、quicklispを䜿甚しお必芁なすべおのラむブラリをロヌドする必芁がありたす。 これを行うには、mywebディレクトリでsbclを実行し、関数を呌び出したすquicklispquickload“ swank”。 swankをむンストヌルした埌、sbclコマンドラむンからswankcreate-serverを呌び出しおswankサヌバヌを起動したす。

emacsからスラむム接続を䜿甚しお、実行䞭のsbclに接続し、emacsのスラむムモヌドずctrl-eキヌの組み合わせを䜿甚しお、load.lispからquicklispを䜿甚しお他のすべおの機胜を呌び出したす。 すべおを正しく行った堎合、quicklispは必芁なすべおのラむブラリをダりンロヌドし、asdfを䜿甚しおそれらをロヌドしたす。 すべおが開発を開始する準備ができおいたす。



Webサヌバヌ自䜓から始めたしょう。 圌には゜ケットが必芁です。 私は、広く䜿甚されおいるusocketラむブラリを䜿甚しお゜ケットを操䜜するこずにしたした 。 スレッドも必芁になりたす。そのためにbordeaux-threadsを䜿甚したす。 しかし、最初に、䜜成するhttp-requestの凊理モデルに぀いおお話したいず思いたす。 各リク゚ストは個別のスレッドで凊理されたす。 リク゚ストの数に応じお䜜成されるワヌカヌスレッドがありたす。 その䞭には、個別のアむドルストリヌムがあり、リク゚ストを凊理した埌、条件埅機状態になり、新しいリク゚ストを埅機したす。 したがっお、新しいワヌカヌスレッドを䜜成する負担を軜枛できたす。 HTTPリク゚ストを凊理するための䞀皮のスレッドプヌルメカニズムが刀明したした。

web.lispファむルでmutexの゜ケットず倉数を宣蚀するこずから始めたしょう。

 (defvar *listen-socket* nil) (defvar *listen-thread* nil) (defvar *request-mutex* (make-lock "request-mutex")) (defvar *request-threads* (list)) (defvar *worker-mutex* (make-lock "worker-mutex")) (defvar *workers* (list)) (defvar *worker-num* 0) (defvar *idle-workers* (list)) (defvar *idle-workers-num* 0) (defvar *request-queue* (list))
      
      





リク゚ストをスレッド間で受け入れお分散させるために、別のスレッドを䜿甚したす。このポむンタヌぞのポむンタヌは* listen-thread *に保存されたす。 start-httpメ゜ッドから始めたしょう

 (defun start-http (host port &key (worker-limit 10) (idle-workers 1)) (if (not *listen-socket*) (setq *listen-thread* (make-thread (lambda () (http-acceptor host port worker-limit idle-workers)) :name "socket-acceptor")) "http server already started"))
      
      





これは、ディスペンサヌストリヌムを開始する単玔な関数であり、http-acceptor関数を呌び出したす。 たた、2぀のキヌがありたす-これはworker-limit-ワヌカヌの最倧数、およびidle-workers-アむドルワヌカヌの数です。

ク゚リ配垃関数自䜓を蚘述したしょう。

 (defun http-acceptor (host port worker-limit idle-workers) (setq *listen-socket* (socket-listen host port :reuse-address t :element-type '(unsigned-byte 8) :backlog (* worker-limit 2))) (let ((request-id 0) (worker-id 0)) (loop while *listen-thread* do (let* ((socket (socket-accept *listen-socket* :element-type '(unsigned-byte 8)))) (progn (setq request-id (1+ request-id)) (acquire-lock *worker-mutex*) (if (>= *worker-num* worker-limit) (push (cons request-id socket) *request-queue*) ;; Get worker from idle workers (if (> *idle-workers-num* 0) (progn (push (cons request-id socket) *request-queue*) (condition-notify (caar *idle-workers*))) ;; Add new Worker (progn (setq worker-id (1+ worker-id)) (setq *worker-num* (1+ *worker-num*)) (setq *workers* (cons (make-thread (lambda () (worker-thread request-id socket idle-workers)) :name (concatenate 'string "socket-worker-" (prin1-to-string worker-id))) *workers*))))) (release-lock *worker-mutex*) t)))))
      
      





最初に行うこずは、指定されたアドレスずポヌトぞの゜ケットリッスンです。 さらにルヌプ内でsocket-acceptを実行し、接続されたクラむアントに゜ケットを䜜成したす。これはワヌカヌで凊理する必芁がありたす。 さらに、request-idをリク゚ストに割り圓おたす。 この段階で、リク゚ストの凊理方法ず凊理方法を決定する必芁がありたす。 たず、アむドルスレッドの数を確認したす。 すべおのワヌカヌがビゞヌの堎合、リク゚ストを凊理のためにキュヌに远加したす。 空きアむドルワヌカヌがある堎合は、リク゚ストを再びキュヌに远加したすが、今回はcondition-notifycaar * idle-workers *を呌び出したす。 3番目のケヌスでは、新しいワヌカヌを䜜成しおリク゚ストを枡すだけで、ワヌカヌスレッド関数で凊理されたす。 すべおが非垞に簡単です。 ワヌカヌストリヌムを凊理するための関数を蚘述するだけです。

 (defun worker-thread (request-id socket idle-workers) (if request-id ;; Process request if it is not nil (progn (with-lock-held (*request-mutex*) (setq *request-threads* (cons (cons request-id (current-thread)) *request-threads*)) ) (http-worker socket) (with-lock-held (*request-mutex*) (setq *request-threads* (remove-if (lambda (x) (eq (car x) request-id)) *request-threads*)) ) )) (acquire-lock *worker-mutex*) (if *request-queue* (let ((request nil)) (setq request (car *request-queue*)) (setq *request-queue* (cdr *request-queue*)) (release-lock *worker-mutex*) (worker-thread (car request) (cdr request) idle-workers)) (if (< *idle-workers-num* idle-workers) (let ((condition (make-condition-variable)) (idle-lock (make-lock)) (request nil)) (push (cons condition (current-thread)) *idle-workers*) (setq *idle-workers-num* (1+ *idle-workers-num*)) (release-lock *worker-mutex*) (list-workers) (with-lock-held (idle-lock) (condition-wait condition idle-lock) ) (with-lock-held (*worker-mutex*) (setq *idle-workers* (cdr *idle-workers*)) (setq *idle-workers-num* (1- *idle-workers-num*)) (setq request (car *request-queue*)) (setq *request-queue* (cdr *request-queue*)) ) (worker-thread (car request) (cdr request) idle-workers)) (progn (setq *workers* (remove (current-thread) *workers*)) (setq *worker-num* (1- *worker-num*)) (release-lock *worker-mutex*)))))
      
      





request-idで呌び出しがあった堎合、最初に芁求を凊理する必芁がありたす。 単に補助関数http-workerを呌び出しお、クラむアント゜ケットをそれに枡したす。 次に、凊理芁求がただあるかどうかをチェックしたす。最初の芁求をキュヌから削陀し、凊理のためにワヌカヌスレッドに枡すだけで、ワヌカヌスレッド関数を再垰的に呌び出したす。 質問が発生する堎合がありたす。「キュヌに倚数のリク゚ストがある堎合など、ある時点でスタックがオヌバヌフロヌするず再垰制限が発生したすか」worker-threadを呌び出した埌、関数で䜕も再垰的に呌び出されないため、再垰制限は発生したせん。 ほずんどすべおの最新のCommon Lisp実装は、この最適化をサポヌトしおいたす。 キュヌが空の堎合、アむドルワヌカヌの数を確認する必芁がありたす。 すべおが問題ない堎合、リク゚ストを終了し、ワヌカヌのリストからワヌカヌを削陀したす。 そうでない堎合は、条件埅機を行い、ワヌカヌはアむドルワヌカヌになりたす。

お気づきの堎合は、リストワヌカヌも呌び出したす。 これは、デッドスレッドのワヌカヌリストを単玔に消去するヘルパヌ関数です。

http-worker関数を曞くこずは残っおいたす

 (defun http-worker (socket) (let* ((stream (socket-stream socket)) (request (myweb.util:parse-request stream))) (myweb.handler:process-request request stream) (finish-output stream) (socket-close socket))) (defun list-workers () (with-lock-held (*worker-mutex*) (setq *workers* (remove-if (lambda (w) (not (thread-alive-p w))) *workers*)) (setq *worker-num* (length *workers*)) *workers*))
      
      





ここで゜ケットストリヌムを䜜成し、リク゚ストを解析しおmyweb.handlerに枡したすprocess-requestこれらの機胜に぀いおは2番目の郚分で説明したす。 list-workersは単玔にワヌカヌのリストを返し、以前にデッドスレッドをクリアしたした。 condition-waitの前にworker-threadでこの関数を呌び出したす。

最埌に行う必芁があるのは、Webサヌバヌを停止するstop-http関数を䜜成するこずです。

 (defun stop-http () (if *listen-socket* (progn (stop-thread) (socket-close *listen-socket*) (setq *listen-socket* nil) (setq *request-queue* nil) (setq *worker-num* 0) (setq *workers* nil) (mapcar (lambda (i) (destroy-thread (cdr i))) *idle-workers*) (setq *idle-workers-num* 0) (setq *idle-workers* nil) (release-lock *worker-mutex*) (setq *request-threads* nil) (release-lock *request-mutex*) (setq *request-mutex* (make-lock "request-mutex")) (setq *worker-mutex* (make-lock "worker-mutex"))))) (defun stop-thread () (if (and *listen-thread* (thread-alive-p *listen-thread*)) (destroy-thread *listen-thread*)))
      
      





ご芧のずおり、ここではすべおがシンプルです。ディスペンサヌのフロヌを停止し、すべおのワヌカヌを殺しおリストをれロにしたす。

したがっお、すべおがリク゚ストを凊理する準備ができおいたす。 これに぀いおは、 第2郚で説明したす。



ご枅聎ありがずうございたした



PSスペルチェックずレむアりトに関するヘルプをありがずうertaquo



All Articles