在节点上的clojurescript的Web框架。

以Django,Flask和Rails的传统。专为快速发货的独立开发人员而设计。战斗在真实站点进行了测试。
哲学|快速启动|文档| API |示例|社区
( ns webserver
( :require
[promesa.core :as p]
[sitefox.html :refer [render]]
[sitefox.web :as web]))
( defn root [_req res]
( ->> ( render [ :h1 " Hello world! " ])
( .send res)))
( p/let [[app host port] ( web/start )]
( .get app " / " root)
( print " Serving on " ( str " http:// " host " : " port)))PORT - 配置端口SiteFox Web服务器绑定到。BIND_ADDRESS配置IP地址SiteFox Web服务器绑定到。SMTP_SERVER配置即将传出的SMTP服务器,例如SMTP_SERVER=smtps://username:[email protected]/?pool=true 。DATABASE_URL配置要连接到的数据库。默认为sqlite://./database.sqlite 。 最快的启动方法是使用一个create脚本之一,它将使用一个命令为您设置示例项目。如果您正在建立一个简单的网站,而没有太多的前端交互性超出表单提交,则NBB创建脚本就是这样:
npm init sitefox-nbb mywebsite
这将创建一个名为mywebsite的文件夹,其中包含您的新项目。请注意,您可以使用Scittle运行CLJS客户端。
如果您正在构建全堆栈clojurescript应用程序,则Shadow-Cljs创建脚本是:
npm init sitefox-shadow-fullstack myapp
这将创建一个名为myapp的文件夹,其中包含您的新项目。
将SiteFox添加到您的项目中,作为一种依赖性:
{:deps
{io.github.chr15m/sitefox {:git/tag "v0.0.26" :git/sha "e6ea2027b5d4277917732d43d550083c8e105da9"}}}
如果您使用的是npm则可以以这种方式安装SiteFox作为依赖关系。如果这样做,则需要以某种方式将node_modules/sitefox/src添加到您的类路径。
npm i sitefox
注意:M1 Mac用户可能需要在NPM中设置Python版本:
npm config set python python3
这是因为node-sqlite3构建有时在没有设置的情况下失败。有关更多详细信息,请参阅此问题。
一个带有两个路由的示例服务器,其中一条将值写入键值数据库。
( ns my.server
( :require
[promesa.core :as p]
[sitefox.web :as web]
[sitefox.db :refer [kv]]))
( defn home-page [req res]
; send a basic hello world response
( .send res " Hello world! " ))
( defn hello [req res]
; write a value to the key-value database
( p/let [table ( kv " sometable " )
r ( .write table " key " 42 )]
( .json res true )))
( defn setup-routes [app]
; flush all routes from express
( web/reset-routes app)
; set up an express route for "/"
( .get app " / " home-page)
; set up an express route for "/hello"
( .post app " /hello " hello)
; statically serve files from the "public" dir on "/"
; (or from "build" dir in PROD mode)
( web/static-folder app " / " " public " ))
( defn main! []
; create an express server and start serving
; BIND_ADDRESS & PORT env vars set host & port.
( p/let [[app _host _port] ( web/start )]
; set up the routes for the first time
( setup-routes app)))这里有更多SiteFox示例。
如果您需要SiteFox的支持,则可以:
SiteFox使用具有明智的会话和记录的明智默认值。有关详细信息,请参见快速路由文档。
使用web/start创建新服务器,并设置一条以“ Hello World!”响应的路线。如下:
( -> ( web/start )
( .then ( fn [app host port]
( .get app " /myroute "
( fn [req res]
( .send res " Hello world! " )))) SiteFox带有可选的系统,可在更改服务器时重新加载路由。每当您的服务器代码刷新时(例如,由Shadow-CLJS构建),您的Express路线都会重新加载。在此示例中,当进行重建时,将调用函数setup-routes 。
( defn setup-routes [app]
; flush all routes from express
( web/reset-routes app)
; ask express to handle the route "/"
( .get app " / " ( fn [req res] ( .send res " Hello world! " ))))
; during the server setup hook up the reloader
( reloader ( partial #'setup-routes app))我推荐Promesa库来管理承诺控制流。此示例假设需要[promesa.core :as p] :
( p/let [[app host port] ( web/start )]
; now use express `app` to set up routes and middleware
)另请参阅以下示例:
SiteFox而不是模板,而是为服务器端试剂渲染,合并的WTH HTML文档提供快捷方式。
[sitefox.html :refer [render-into]]您可以将HTML文档加载并渲染试剂表格为选定的元素:
( def index-html ( fs/readFileSync " index.html " ))
( defn component-main []
[ :div
[ :h1 " Hello world! " ]
[ :p " This is my content. " ]])
; this returns a new HTML string that can be returned
; e.g. with (.send res)
( render-into index-html " main " [component-main])SiteFox使用Node-HTML-Parser,并提供用于与HTML和试剂合作的快捷方式:
html/parse是node-html-parser/parse的速记。html/render是试剂render-to-static-markup速记。html/$是解析器的querySelector的速记。html/$$是解析器的querySelectorAll的速记。另请参阅模板示例项目。
SiteFox使得无需配置即可开始存储键值数据。如果需要,您可以稍后过渡到更多结构化数据。它将KeyV捆绑在一起,这是一个数据库备份的密钥值商店。您可以通过db/kv访问密钥值商店,并通过db/client访问基础数据库。
请参阅DB模块的完整文档。
默认情况下,使用本地SQLITE数据库,您可以立即开始在服务器上持续数据,而无需任何配置。转移到生产后,您可以使用环境变量DATABASE_URL配置另一个数据库。例如,要使用称为“ dbname”的Postgres数据库,您可以按以下方式访问它(取决于您的网络/本地设置):
DATABASE_URL="postgres://%2Fvar%2Frun%2Fpostgresql/DBNAME"
DATABASE_URL=postgres://someuser:somepassword@somehost:5432/DBNAME
DATABASE_URL=postgres:///somedatabase
请注意,如果要使用Postgres后端,则还需要npm install @keyv/postgres 。
要使用数据库和键值接口首先需要数据库模块:
[sitefox.db :as db]现在,您可以使用db/kv将键值写入名称为“表”:
( let [table ( db/kv " sometable " )]
( .set table " key " " 42 " ))再次检索值:
( -> ( .get table " key " )
( .then ( fn [val] ( print val))))您可以使用db/client访问基础数据库客户端。例如,对配置的数据库进行查询:
( let [c ( db/client )]
( -> ( .query c " select * from sometable WHERE x = 1 " )
( .then ( fn [rows] ( print rows)))))同样,建议使用Promesa来管理数据库操作期间的控制流。
要探索命令行的键值数据,请使用SQLite和JQ来过滤数据:
sqlite3 database.sqlite "select * from keyv where key like 'SOMEPREFIX%';" | cut -f 2 -d "|" | jq '.'
默认情况下, node-sqlite3模块不会为数据库错误时提供完整的堆栈跟踪。可以以较小的性能罚款打开详细的堆栈痕迹,如下所示:
( ns yourapp
( :require
[ " sqlite3 " :as sqlite3]))
( .verbose sqlite3)如果要在生产中运行sqlite3,则可能会遇到错误SQLITE_BUSY: database is locked 。可以通过在sqlite3中启用写入记录模式来解决这些并发问题和锁定问题,如下所示:
(ns yourapp
(:require
[sitefox.db :refer [client]]))
(p/let [c (client)
wal-mode-enabled (.query c "PRAGMA journal_mode=WAL;")]
(js/console.log wal-mode-enabled))
该代码可以安全地放置在服务器代码的主要功能中。
默认情况下启用了会话,您的服务器的每个访问者都会有自己的会话。会话数据在页面加载之间持续存在,因此您可以使用它来存储身份验证状态。会话备份到名称kv表中(请参见上面的数据库部分)。您可以使用req.session读取和编写任意JS数据结构。
为会话存储(在路由处理程序功能内部)写一个值:
( let [session ( aget req " session " )]
( aset session " myvalue " 42 ))从会话商店读取值:
( aget req " session " " myvalue " )SiteFox包装护照库以实现身份验证。您可以使用三个函数调用将简单的电子邮件和基于密码的身份验证添加到您的应用程序:
( defn setup-routes [app]
( let [template ( fs/readFileSync " index.html " )]
( web/reset-routes app)
; three calls to set up email based authentication
( auth/setup-auth app)
( auth/setup-email-based-auth app template " main " )
( auth/setup-reset-password app template " main " )
; ... add your additional routes here ... ;
))传递的template字符串是一个HTML文档, "main"是指定载体UI的选择器。默认情况下,这将设置以下路线,您可以发送用户注册,登录并重置其密码:
/auth/sign-in/auth/sign-up/auth/reset-password还可以覆盖默认的Auth UI试剂表单和重定向URL,以使用您自己的版本自定义它们。有关如何提供您自己的试剂表格的详细信息,请参见验证文档。还要查看默认试剂验证表格的源代码,如果您想制作自己的源代码。
当用户签署其数据时,将持续到SiteFox使用的默认KEYV数据库中。您可以在请求对象上检索当前身份验证的用户的数据架构:
( let [user ( aget req " user " )] ...)然后,您可以更新用户的数据并将其数据保存回数据库。 applied-science.js-interop库很方便(在此要求为j ):
( p/let [user ( aget req " user " )]
( j/assoc! user :somekey 42 )
( auth/save-user user))如果要创建一个新表,则可以在用户的UUID上键入(aget user "id")很有用。
有关更多详细信息,请参见身份验证示例。
要添加一个新的身份验证方案,例如基于用户名或第三方Oauth,请咨询Passport Docs和Auth.Cljs。拉请最欢迎!
SiteFox将NodeMailer捆绑以发送电子邮件。配置您的传出SMTP服务器:
SMTP_SERVER=smtps://username:[email protected]/?pool=true
然后,您可以使用以下方式使用send-email函数:
( -> ( mail/send-email
" [email protected] "
" [email protected] "
" This is my test email. "
:text " Hello, This is my first email from **Sitefox**. Thank you. " )
( .then js/console.log))默认情况下,发送的电子邮件以JSON-LINES格式记录到./logs/mail.log 。
如果您没有指定SMTP服务器,则电子邮件模块将处于调试模式。不会发送任何电子邮件,即将发出的电子邮件写信给/tmp进行检查,并且send-email结果也将记录到控制台。
如果设置SMTP_SERVER=ethereal则将使用Ethereal.Email服务。运行send-email后,您可以打印结果的url属性。您可以使用打印的链接在开发模式下测试您的电子邮件。
另请参阅“发送电子邮件”示例项目。
请参阅使用节点输入 - validator并检查CSRF问题的表单验证示例。
为了确保您可以在没有CSRF警告的POST您应该创建这样的隐藏元素(试剂语法):
[ :input { :name " _csrf " :type " hidden " :default-value ( .csrfToken req)}]如果您要从客户端提出AJAX POST请求,则应将CSRF令牌作为标头传递。有效的令牌可以在JSON Endpoint /_csrf-token上作为字符串获得,您可以使用fetch-csrf-token获取它,并将其添加到Fetch请求的标题中,如下所示:
( ns n ( :require [sitefox.ui :refer [fetch-csrf-token]]))
( -> ( fetch-csrf-token )
( .then ( fn [token]
( js/fetch " /api/endpoint "
#js { :method " POST "
:headers #js { :Content-Type " application/json "
:X-XSRF-TOKEN token} ; <- use token here
:body ( js/JSON.stringify ( clj->js some-data))}))))注意:如果设置环境变量SEND_CSRF_TOKEN ,则可以从客户端cookie获取CSRF令牌。这是以前的SiteFox版本中的默认值。设置后,SiteFox将在客户端cookie XSRF-TOKEN中的每个GET请求上发送令牌,并且可以使用ui/csrf-token功能检索。这是CSRF保护的有效但不太安全的形式。
在某些极少数情况下,您可能希望关闭CSRF检查(例如,从非浏览器设备发布到API)。如果您知道自己在做什么,则可以使用pre-csrf-router添加绕过CSRF检查的路由:
( defn setup-routes [app]
; flush all routes from express
( web/reset-routes app)
; set up an API route bypassing CSRF checks
( .post ( j/get app " pre-csrf-router " ) " /api/endpoint " endpoint-unprotected-by-csrf)
; set up an express route for "/hello" which is protected as normal
( .post app " /hello " hello))默认情况下,Web服务器将写入./logs文件夹中的日志文件。这些文件由服务器自动旋转。日志有两种类型:
logs/access.log是标准的Web访问日志,“合并”格式。logs/error.log在其中使用tracebacks/install-traceback-handler编写回溯。向错误日志发送未知的例外:
(def admin-email (env-required "ADMIN_EMAIL"))
(def build-id (try (fs/readFileSync "build-id.txt") (catch :default _e "dev")))
(install-traceback-handler admin-email build-id)
基于当前的git commit创建build-id.txt如下:
git rev-parse HEAD | cut -b -8 > build-id.txt
如果要在追溯中获得正确的clojurescript行号,则需要["source-maps-support" :as sourcemaps] ,然后:
(.install sourcemaps)
您可以使用web/setup-error-handler功能根据您定义的试剂组件为这些错误提供页面:
( defn component-error-page [_req error-code error]
[ :section.error
[ :h2 error-code " Error " ]
( case error-code
404 [ :p " We couldn't find the page you're looking for. " ]
500 [ :<> [ :p " An error occurred: " ] [ :p ( .toString error)]]
[ :div " An unknown error occurred. " ])])
( web/setup-error-handler app my-html-template " main " component-error-page)您可以将它们结合起来,以捕获500个内部服务器错误和未接收的例外,如下所示:
(let [traceback-handler (install-traceback-handler admin-email build-id)]
(web/setup-error-handler app template-app "main" component-error-page traceback-handler))
使用nbb和shadow-cljs支持实时重新加载。默认情况下,使用NPM创建脚本时启用它。示例有更多详细信息。
SiteFox由Chris McCormick(Twitter上的@MCCRMX和@[email protected]上的mastodon上)制作。我在为自己和客户建立网站时迭代了它。