Skip to content

Instantly share code, notes, and snippets.

@svetlyak40wt
Created February 11, 2022 16:36
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save svetlyak40wt/748cf976df847df6d0fee1a38aef1251 to your computer and use it in GitHub Desktop.
Save svetlyak40wt/748cf976df847df6d0fee1a38aef1251 to your computer and use it in GitHub Desktop.
Example of a simple file uploader using Common Lisp and Ningle framework
;; License: MIT
(uiop:define-package #:ningle-upload
(:use #:cl)
(:import-from #:cl-fad)
(:import-from #:ningle)
(:import-from #:spinneret)
(:import-from #:log4cl))
(in-package #:ningle-upload)
(defvar *app* (make-instance 'ningle:app))
(defvar *server* nil)
(defparameter *downloads-dir* #P"/tmp/downloads/")
(setf (ningle:route *app* "/")
(lambda (params)
(declare (ignore params))
(spinneret:with-html-string
(:h1 "Ningle Uploader Example")
(:h2 "Already Uploaded")
(let ((files
(when (probe-file *downloads-dir*)
(cl-fad:list-directory *downloads-dir*))))
(if files
(:ul
(loop for file in files
do (:li (:p (file-namestring file)))))
(:p "There is no files yet.")))
(:h2 "Add More")
(:form :method "POST"
:action "/"
:enctype "multipart/form-data"
(:input :type "file"
:multiple t
:name "upload")
(:input :type "submit")))))
(setf (ningle:route *app* "/" :method :POST)
(lambda (params)
;; In case of multiple files, params will have a multiple "upload"
;; entries:
;;
;; (("upload" #<FLEXI-STREAMS::VECTOR-INPUT-STREAM {700821F9F3}>
;; "file1.png" "image/png")
;; ("upload" #<FLEXI-STREAMS::VECTOR-INPUT-STREAM {7008223413}>
;; "file2.png" "image/png")
;; ("upload" #<FLEXI-STREAMS::VECTOR-INPUT-STREAM {7008223F23}>
;; "file3.png" "image/png"))
;;
;; And we need to process them one by one:
(loop for row in params
for name = (first row)
when (string= name "upload")
;; Here stream will have type FLEXI-STREAMS::VECTOR-INPUT-STREAM
do (destructuring-bind (stream filename content-type)
(rest row)
(when stream
(log:info "Accepting upload." filename content-type)
(let ((full-path (merge-pathnames filename *downloads-dir*)))
(ensure-directories-exist full-path)
(uiop:slurp-input-stream full-path stream))))
finally
;; Redirecting user back to the list of files:
(setf (lack.response:response-status ningle:*response*)
302
(lack.response:response-headers ningle:*response*)
(append (lack.response:response-headers ningle:*response*)
(list :location "/"))))))
(defun start (&key (interface "localhost")
(port 8080))
(setf *server*
(clack:clackup *app*
:address interface
:port port)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment