Citizen เป็นเฟรมเวิร์กเว็บแอปพลิเคชันที่ใช้ MVC ที่ออกแบบมาสำหรับผู้ที่สนใจในการสร้างเว็บไซต์ที่รวดเร็วและปรับขนาดได้อย่างรวดเร็วแทนที่จะขุดรอบ ๆ ความกล้าของโหนดหรือปูด้วยกันหอคอย Jenga ที่สั่นคลอนจาก 50 แพ็คเกจที่แตกต่างกัน
ใช้ Citizen เป็นรากฐานสำหรับแอปพลิเคชันเว็บฝั่งเซิร์ฟเวอร์แบบดั้งเดิมแอปพลิเคชันหน้าเดียวแบบแยกส่วน (SPA) หรือ API ที่อยู่อาศัย
มีการเปลี่ยนแปลงจำนวนมากในการเปลี่ยนแปลงจาก 0.9.x เป็น 1.0.x โปรดปรึกษา Changelog สำหรับรายการที่แยกรายการและตรวจสอบเอกสารที่อัปเดตนี้อย่างละเอียด
เห็นได้ชัดว่านี่เป็นวิธีที่มีเนื้อหามากกว่า NPM/GitHub readme ใด ๆ ที่ควรมี ฉันกำลังทำงานบนเว็บไซต์สำหรับเอกสารนี้
ฉันใช้ Citizen บนเว็บไซต์ส่วนตัวของฉันและ OriginalTrilogy.com OT.com จัดการปริมาณการใช้งานในระดับปานกลาง (ไม่กี่แสนครั้งในแต่ละเดือน) ในแผนการโฮสติ้งคลาวด์ $ 30 ที่ใช้อินสแตนซ์เดียวของพลเมืองที่แอป/กระบวนการทำงานเป็นเวลาหลายเดือนโดยไม่ต้องล่ม มันมีเสถียรภาพมาก
คำสั่งเหล่านี้จะสร้างไดเรกทอรีใหม่สำหรับเว็บแอปของคุณติดตั้งพลเมืองใช้ยูทิลิตี้นั่งร้านเพื่อสร้างโครงกระดูกของแอปและเริ่มเว็บเซิร์ฟเวอร์:
$ mkdir myapp && cd myapp
$ npm install citizen
$ node node_modules/citizen/util/scaffold skeleton
$ node app/start.jsหากทุกอย่างเป็นไปด้วยดีคุณจะเห็นการยืนยันในคอนโซลที่เว็บเซิร์ฟเวอร์กำลังทำงานอยู่ ไปที่ http://127.0.0.1:3000 ในเบราว์เซอร์ของคุณแล้วคุณจะเห็นเทมเพลตดัชนีเปล่า
Citizen ใช้ตัวอักษรเทมเพลตในเอ็นจิ้นเทมเพลตเริ่มต้น คุณสามารถติดตั้งรวมอัปเดตการกำหนดค่าเทมเพลตและแก้ไขเทมเพลตมุมมองเริ่มต้นตามลำดับ
สำหรับตัวเลือกการกำหนดค่าดูการกำหนดค่า สำหรับยูทิลิตี้เพิ่มเติมเพื่อช่วยให้คุณเริ่มต้นดูสาธารณูปโภค
app/
config/ // These files are all optional
citizen.json // Default config file
local.json // Examples of environment configs
qa.json
prod.json
controllers/
hooks/ // Application event hooks (optional)
application.js
request.js
response.js
session.js
routes/ // Public route controllers
index.js
helpers/ // Utility modules (optional)
models/ // Models (optional)
index.js
views/
error/ // Default error views
404.html
500.html
ENOENT.html
error.html
index.html // Default index view
start.js
logs/ // Log files
access.log
error.log
web/ // public static assets
นำเข้าพลเมืองและเริ่มแอปของคุณ:
// start.js
import citizen from 'citizen'
global . app = citizen
app . start ( )วิ่งจากเทอร์มินัล:
$ node start.jsคุณสามารถกำหนดค่าแอพพลิเคชั่นพลเมืองของคุณด้วยไฟล์กำหนดค่าตัวเลือกการเริ่มต้นและ/หรือการกำหนดค่าคอนโทรลเลอร์ที่กำหนดเอง
ไดเรกทอรี config เป็นตัวเลือกและมีไฟล์การกำหนดค่าในรูปแบบ JSON ที่ขับเคลื่อนทั้งพลเมืองและแอพของคุณ คุณสามารถมีไฟล์การกำหนดค่าพลเมืองหลายไฟล์ภายในไดเรกทอรีนี้เพื่อให้การกำหนดค่าที่แตกต่างกันตามสภาพแวดล้อม Citizen สร้างการกำหนดค่าตามลำดับชั้นต่อไปนี้:
host ที่ตรงกับชื่อโฮสต์ของเครื่องและหากพบไฟล์หนึ่งให้ขยายการกำหนดค่าเริ่มต้นด้วยการกำหนดค่าไฟล์host ที่ตรงกันมันจะมองหาไฟล์ชื่อ Citizen.json และโหลดการกำหนดค่านั้นสมมติว่าคุณต้องการเรียกใช้พลเมืองบนพอร์ต 8080 ในสภาพแวดล้อม dev ในพื้นที่ของคุณและคุณมีฐานข้อมูลท้องถิ่นแอปของคุณจะเชื่อมต่อ คุณสามารถสร้างไฟล์กำหนดค่าที่เรียกว่า local.json (หรือ dev.json ไม่ว่าคุณต้องการอะไร) ด้วยสิ่งต่อไปนี้:
{
"host" : "My-MacBook-Pro.local" ,
"citizen" : {
"mode" : "development" ,
"http" : {
"port" : 8080
}
} ,
"db" : {
"server" : "localhost" , // app.config.db.server
"username" : "dbuser" , // app.config.db.username
"password" : "dbpassword" // app.config.db.password
}
}การกำหนดค่านี้จะขยายการกำหนดค่าเริ่มต้นเฉพาะเมื่อทำงานบนเครื่องในเครื่องของคุณ การใช้วิธีนี้คุณสามารถสร้างไฟล์กำหนดค่าหลายไฟล์จากสภาพแวดล้อมที่แตกต่างกันไปยังที่เก็บเดียวกัน
การตั้งค่าฐานข้อมูลจะสามารถเข้าถึงได้ทุกที่ภายในแอปของคุณผ่าน app.config.db citizen และโหนด host ถูกสงวนไว้สำหรับเฟรมเวิร์ก สร้างโหนดของคุณเองเพื่อจัดเก็บการตั้งค่าที่กำหนดเองของคุณ
คุณสามารถตั้งค่าการกำหนดค่าแอปของคุณเมื่อเริ่มต้นผ่าน app.start() หากมีไฟล์กำหนดค่าการกำหนดค่าเริ่มต้นจะขยายไฟล์กำหนดค่า หากไม่มีไฟล์กำหนดค่าการกำหนดค่าเริ่มต้นจะขยายการกำหนดค่าพลเมืองเริ่มต้น
// Start an HTTPS server with a PFX file
app . start ( {
citizen : {
http : {
enabled : false
} ,
https : {
enabled : true ,
pfx : '/absolute/path/to/site.pfx'
}
}
} ) ในการตั้งค่าการกำหนดค่าที่กำหนดเองที่ระดับคอนโทรลเลอร์เส้นทางส่งออกวัตถุ config (เพิ่มเติมเกี่ยวกับตัวควบคุมเส้นทางและการกระทำในส่วนตัวควบคุมเส้นทาง)
export const config = {
// The "controller" property sets a configuration for all actions in this controller
controller : {
contentTypes : [ 'application/json' ]
}
// The "submit" property is only for the submit() controller action
submit : {
form : {
maxPayloadSize : 1000000
}
}
} ต่อไปนี้แสดงถึงการกำหนดค่าเริ่มต้นของ Citizen ซึ่งขยายโดยการกำหนดค่าของคุณ:
{
host : '' ,
citizen : {
mode : process . env . NODE_ENV || 'production' ,
global : 'app' ,
http : {
enabled : true ,
hostname : '127.0.0.1' ,
port : 80
} ,
https : {
enabled : false ,
hostname : '127.0.0.1' ,
port : 443 ,
secureCookies : true
} ,
connectionQueue : null ,
templateEngine : 'templateLiterals' ,
compression : {
enabled : false ,
force : false ,
mimeTypes : [
'application/javascript' ,
'application/x-javascript' ,
'application/xml' ,
'application/xml+rss' ,
'image/svg+xml' ,
'text/css' ,
'text/html' ,
'text/javascript' ,
'text/plain' ,
'text/xml'
]
} ,
sessions : {
enabled : false ,
lifespan : 20 // minutes
} ,
layout : {
controller : '' ,
view : ''
} ,
contentTypes : [
'text/html' ,
'text/plain' ,
'application/json' ,
'application/javascript'
] ,
forms : {
enabled : true ,
maxPayloadSize : 524288 // 0.5MB
} ,
cache : {
application : {
enabled : true ,
lifespan : 15 , // minutes
resetOnAccess : true ,
encoding : 'utf-8' ,
synchronous : false
} ,
static : {
enabled : false ,
lifespan : 15 , // minutes
resetOnAccess : true
} ,
invalidUrlParams : 'warn' ,
control : { }
} ,
errors : 'capture' ,
logs : {
access : false , // performance-intensive, opt-in only
error : {
client : true , // 400 errors
server : true // 500 errors
} ,
debug : false ,
maxFileSize : 10000 ,
watcher : {
interval : 60000
}
} ,
development : {
debug : {
scope : {
config : true ,
context : true ,
cookie : true ,
form : true ,
payload : true ,
route : true ,
session : true ,
url : true ,
} ,
depth : 4 ,
showHidden : false ,
view : false
} ,
watcher : {
custom : [ ] ,
killSession : false ,
ignored : / (^|[/\]).. / // Ignore dotfiles
}
} ,
urlPath : '/' ,
directories : {
app : < appDirectory > ,
controllers : < appDirectory > + '/controllers',
helpers : < appDirectory > + '/helpers',
models : < appDirectory > + '/models',
views : < appDirectory > + '/views',
logs : new URL('../../../logs', import.meta.url).pathname
web : new URL('../../../web', import.meta.url).pathname
}
}
} นี่คือบทสรุปที่สมบูรณ์ของการตั้งค่าของพลเมืองและสิ่งที่พวกเขาทำ
เมื่อเริ่มต้นเซิร์ฟเวอร์นอกเหนือจากตัวเลือกการกำหนดค่า http และ https ของ Citizen คุณสามารถให้ตัวเลือกเดียวกันกับ HTTP.Createserver () และ https.createServer () ของโหนด
ความแตกต่างเพียงอย่างเดียวคือวิธีที่คุณผ่านไฟล์คีย์ อย่างที่คุณเห็นในตัวอย่างด้านบนคุณผ่านพลเมืองเส้นทางไฟล์สำหรับไฟล์คีย์ของคุณ Citizen อ่านไฟล์ให้คุณ
| การตั้งค่า | พิมพ์ | ค่าเริ่มต้น | คำอธิบาย |
|---|---|---|---|
host | สาย | '' | ในการโหลดไฟล์ config ที่แตกต่างกันในสภาพแวดล้อมที่แตกต่างกัน Citizen อาศัยชื่อโฮสต์ของเซิร์ฟเวอร์เป็นคีย์ เมื่อเริ่มต้นหากพลเมืองค้นหาไฟล์กำหนดค่าที่มีคีย์ host ที่ตรงกับชื่อโฮสต์ของเซิร์ฟเวอร์จะเลือกไฟล์กำหนดค่านั้น สิ่งนี้ไม่ควรสับสนกับ hostname เซิร์ฟเวอร์ HTTP (ดูด้านล่าง) |
| พลเมือง | |||
mode | สาย | ตรวจสอบ NODE_ENV ก่อน production | โหมดแอปพลิเคชันกำหนดพฤติกรรมรันไทม์บางอย่าง ค่าที่เป็นไปได้คือ production และ development โหมดการผลิตความเงียบลงบันทึกคอนโซล โหมดการพัฒนาเปิดใช้งานบันทึกคอนโซล verbose ตัวเลือกการดีบัก URL และการเปลี่ยนโมดูลร้อน |
global | สาย | app | อนุสัญญาสำหรับการเริ่มต้นพลเมืองในไฟล์เริ่มต้นกำหนดกรอบการทำงานให้กับตัวแปรส่วนกลาง ค่าเริ่มต้นซึ่งคุณจะเห็นการอ้างอิงตลอดทั้งเอกสารคือ app คุณสามารถเปลี่ยนการตั้งค่านี้หากคุณต้องการใช้ชื่ออื่น |
contentTypes | อาร์เรย์ | [ 'text/html', 'text/plain', 'application/json', 'application/javascript' ] | Formats ของรูปแบบการตอบกลับสำหรับแต่ละคำขอตามส่วนหัวคำขอ Accept ของลูกค้า เมื่อกำหนดค่ารูปแบบที่มีอยู่สำหรับตัวควบคุมเส้นทางหรือการกระทำแต่ละรูปแบบจะต้องจัดทำรูปแบบที่มีอยู่ทั้งหมด |
errors | สาย | capture | เมื่อแอปพลิเคชันของคุณเกิดข้อผิดพลาดพฤติกรรมเริ่มต้นคือให้พลเมืองพยายามกู้คืนจากข้อผิดพลาดและทำให้แอปพลิเคชันทำงานอยู่ การตั้งค่าตัวเลือกนี้เพื่อ exit จะบอกให้พลเมืองบันทึกข้อผิดพลาดและออกจากกระบวนการแทน |
templateEngine | สาย | templateLiterals | Citizen ใช้ [template lalalal] (https://developer.mozilla.org/en-us/docs/web/javascript/reference/template_literals) ไวยากรณ์สำหรับการแสดงผลโดยค่าเริ่มต้น ทางเลือกคุณสามารถติดตั้งการรวมและใช้เอ็นจิ้นใด ๆ ที่รองรับ (ตัวอย่างเช่นติดตั้งแฮนด์บาร์และตั้งค่า templateEngine เป็น handlebars ) |
urlPath | สาย | / | หมายถึงเส้นทาง URL ที่นำไปสู่แอปของคุณ หากคุณต้องการให้แอปของคุณสามารถเข้าถึงได้ผ่าน http://yoursite.com/my/app และคุณไม่ได้ใช้เซิร์ฟเวอร์อื่นเป็นส่วนหน้าเพื่อส่งคำขอการตั้งค่านี้ควรเป็น /my/app (อย่าลืม Slash ชั้นนำ) การตั้งค่านี้เป็นสิ่งจำเป็นสำหรับเราเตอร์ในการทำงาน |
| http | |||
enabled | บูลีน | true | เปิดใช้งานเซิร์ฟเวอร์ HTTP |
hostname | สาย | 127.0.0.1 | ชื่อโฮสต์ที่แอปของคุณสามารถเข้าถึงได้ผ่าน HTTP คุณสามารถระบุสตริงว่างเพื่อรับคำขอได้ที่ชื่อโฮสต์ใด ๆ |
port | ตัวเลข | 3000 | หมายเลขพอร์ตที่เซิร์ฟเวอร์ HTTP ของ Citizen รับฟังการร้องขอ |
| https | |||
enabled | บูลีน | false | เปิดใช้งานเซิร์ฟเวอร์ HTTPS |
hostname | สาย | 127.0.0.1 | ชื่อโฮสต์ที่แอปของคุณสามารถเข้าถึงได้ผ่าน HTTPS ค่าเริ่มต้นคือ localhost แต่คุณสามารถระบุสตริงว่างเพื่อรับคำขอได้ที่ชื่อโฮสต์ใด ๆ |
port | ตัวเลข | 443 | หมายเลขพอร์ตที่เซิร์ฟเวอร์ HTTPS ของ Citizen รับฟังการร้องขอ |
secureCookies | บูลีน | true | โดยค่าเริ่มต้นคุกกี้ทั้งหมดที่ตั้งอยู่ภายในคำขอ HTTPS มีความปลอดภัย ตั้งค่าตัวเลือกนี้ให้เป็น false เพื่อแทนที่พฤติกรรมนั้นทำให้คุกกี้ทั้งหมดไม่ปลอดภัยและกำหนดให้คุณตั้งค่าตัวเลือก secure ในคำสั่งคุกกี้ด้วยตนเอง |
connectionQueue | จำนวนเต็ม | null | จำนวนสูงสุดของการร้องขอขาเข้าสู่คิว หากปล่อยทิ้งไว้อย่างไม่ระบุรายละเอียดระบบปฏิบัติการจะกำหนดขีด จำกัด ของคิว |
| การประชุม | |||
enabled | บูลีน | false | เปิดใช้งานขอบเขตเซสชันของผู้ใช้ซึ่งกำหนด ID ที่ไม่ซ้ำกันของผู้เข้าชมแต่ละคนและช่วยให้คุณสามารถจัดเก็บข้อมูลที่เชื่อมโยงกับ ID นั้นภายในแอปพลิเคชันเซิร์ฟเวอร์ |
lifespan | จำนวนเต็มบวก | 20 | หากเปิดใช้งานเซสชันหมายเลขนี้แสดงถึงความยาวของเซสชันของผู้ใช้ในไม่กี่นาที เซสชั่นจะหมดอายุโดยอัตโนมัติหากผู้ใช้ไม่ได้ใช้งานในระยะเวลานี้ |
| เค้าโครง | |||
controller | สาย | '' | หากคุณใช้ตัวควบคุมเค้าโครงทั่วโลกคุณสามารถระบุชื่อของคอนโทรลเลอร์นั้นได้ที่นี่แทนที่จะใช้คำสั่ง next ในคอนโทรลเลอร์ทั้งหมดของคุณ |
view | สาย | '' | โดยค่าเริ่มต้นคอนโทรลเลอร์เค้าโครงจะใช้มุมมองเค้าโครงเริ่มต้น แต่คุณสามารถระบุมุมมองอื่นได้ที่นี่ ใช้ชื่อไฟล์โดยไม่ต้องขยายไฟล์ |
| รูปแบบ | |||
enabled | บูลีน | true | พลเมืองให้การประมวลผลภาระพื้นฐานสำหรับรูปแบบง่าย ๆ หากคุณต้องการใช้แพ็คเกจฟอร์มแยกต่างหากให้ตั้งค่าเป็น false |
maxPayloadSize | จำนวนเต็มบวก | 524288 | ขนาดน้ำหนักบรรทุกสูงสุดในไบต์ ตั้งค่าขนาดน้ำหนักสูงสุดเพื่อป้องกันไม่ให้เซิร์ฟเวอร์ของคุณถูกโอเวอร์โหลดโดยข้อมูลอินพุตแบบฟอร์ม |
| การบีบอัด | |||
enabled | บูลีน | false | เปิดใช้งานการบีบอัด GZIP และ deflate สำหรับมุมมองที่แสดงผลและสินทรัพย์คงที่ |
force | บูลีนหรือสตริง | false | กองกำลัง GZIP หรือการเข้ารหัสแบบยุบสำหรับลูกค้าทุกคนแม้ว่าพวกเขาจะไม่รายงานการยอมรับรูปแบบการบีบอัด พร็อกซีและไฟร์วอลล์จำนวนมากทำลายส่วนหัวการเข้ารหัสที่ยอมรับได้ซึ่งกำหนดการสนับสนุน GZIP และเนื่องจากลูกค้าที่ทันสมัยทุกคนรองรับ GZIP จึงมักจะปลอดภัยที่จะบังคับโดยการตั้งค่านี้เป็น gzip แต่คุณสามารถบังคับให้ deflate ได้ |
mimeTypes | อาร์เรย์ | ดูการกำหนดค่าเริ่มต้นด้านบน | อาร์เรย์ประเภท MIME ที่จะถูกบีบอัดหากเปิดใช้งานการบีบอัด ดูตัวอย่างการกำหนดค่าด้านบนสำหรับรายการเริ่มต้น หากคุณต้องการเพิ่มหรือลบรายการคุณต้องเปลี่ยนอาร์เรย์อย่างครบถ้วน |
| แคช | |||
control | วัตถุที่มีคีย์/ค่าคู่ | {} | ใช้การตั้งค่านี้เพื่อตั้งค่าส่วนหัวควบคุมแคชสำหรับตัวควบคุมเส้นทางและสินทรัพย์คงที่ กุญแจสำคัญคือชื่อพา ธ ของสินทรัพย์และค่าคือส่วนหัวควบคุมแคช ดูการแคชฝั่งไคลเอ็นต์สำหรับรายละเอียด |
invalidUrlParams | สาย | warn | ตัวเลือกเส้นทางแคชสามารถระบุพารามิเตอร์ URL ที่ถูกต้องเพื่อป้องกันไม่ให้ URL ที่ไม่ดีถูกแคชและ invalidUrlParams กำหนดว่าจะบันทึกคำเตือนเมื่อพบ URL ที่ไม่ดีหรือโยนข้อผิดพลาดฝั่งไคลเอ็นต์ ดูคำขอแคชและการดำเนินการคอนโทรลเลอร์สำหรับรายละเอียด |
| แคชแอปพลิเคชัน | |||
enabled | บูลีน | true | เปิดใช้งานแคชในหน่วยความจำที่เข้าถึงได้ผ่านวิธี cache.set() และ cache.get() |
lifespan | ตัวเลข | 15 | ระยะเวลาที่สินทรัพย์แอปพลิเคชันแคชยังคงอยู่ในหน่วยความจำในไม่กี่นาที |
resetOnAccess | บูลีน | true | กำหนดว่าจะรีเซ็ตตัวจับเวลาแคชบนสินทรัพย์แคชเมื่อใดก็ตามที่มีการเข้าถึงแคชหรือไม่ เมื่อตั้งค่าเป็น false รายการแคชจะหมดอายุเมื่อถึง lifespan |
encoding | สาย | utf-8 | เมื่อคุณผ่านเส้นทางไฟล์ไปยัง cache.set () การตั้งค่าการเข้ารหัสจะกำหนดว่าควรใช้การเข้ารหัสเมื่ออ่านไฟล์ |
synchronous | บูลีน | false | เมื่อคุณผ่านเส้นทางไฟล์ไปยัง cache.set () การตั้งค่านี้จะกำหนดว่าควรอ่านไฟล์แบบซิงโครนัสหรือแบบอะซิงโครนัส โดยค่าเริ่มต้นการอ่านไฟล์เป็นแบบอะซิงโครนัส |
| cache.static | |||
enabled | บูลีน | false | เมื่อให้บริการไฟล์แบบคงที่พลเมืองมักจะอ่านไฟล์จากดิสก์สำหรับแต่ละคำขอ คุณสามารถเพิ่มความเร็วไฟล์แบบคงที่ที่ให้บริการได้อย่างมากโดยการตั้งค่านี้เป็น true ซึ่งแคชบัฟเฟอร์ไฟล์ในหน่วยความจำ |
lifespan | ตัวเลข | 15 | ระยะเวลาที่สินทรัพย์คงที่ที่แคชยังคงอยู่ในหน่วยความจำในไม่กี่นาที |
resetOnAccess | บูลีน | true | กำหนดว่าจะรีเซ็ตตัวจับเวลาแคชบนสินทรัพย์คงที่แคชเมื่อใดก็ตามที่มีการเข้าถึงแคชหรือไม่ เมื่อตั้งค่าเป็น false รายการแคชจะหมดอายุเมื่อถึง lifespan |
| ท่อนไม้ | |||
access | บูลีน | false | เปิดใช้งานไฟล์บันทึกการเข้าถึง HTTP ปิดการใช้งานโดยค่าเริ่มต้นเนื่องจากบันทึกการเข้าถึงสามารถระเบิดได้อย่างรวดเร็วและในอุดมคติควรได้รับการจัดการโดยเว็บเซิร์ฟเวอร์ |
debug | บูลีน | false | เปิดใช้งานไฟล์บันทึกการดีบัก มีประโยชน์สำหรับการดีบักปัญหาการผลิต แต่ verbose มาก (บันทึกเดียวกับที่คุณเห็นในคอนโซลในโหมดการพัฒนา) |
maxFileSize | ตัวเลข | 10000 | กำหนดขนาดไฟล์สูงสุดของไฟล์บันทึกเป็นกิโลไบต์ เมื่อถึงขีด จำกัด ไฟล์บันทึกจะถูกเปลี่ยนชื่อด้วยการประทับเวลาและไฟล์บันทึกใหม่ถูกสร้างขึ้น |
| บันทึกข้อผิดพลาด | |||
client | บูลีน | true | เปิดใช้งานการบันทึกข้อผิดพลาดของไคลเอนต์ 400 ระดับ |
server | บูลีน | false | เปิดใช้งานการบันทึกข้อผิดพลาดเซิร์ฟเวอร์/แอปพลิเคชันระดับ 500 ระดับ |
status | บูลีน | false | ควบคุมว่าควรบันทึกข้อความสถานะไปยังคอนโซลเมื่ออยู่ในโหมดการผลิตหรือไม่ (โหมดการพัฒนาบันทึกไปยังคอนโซลเสมอ) |
| logs.watcher | |||
interval | ตัวเลข | 60000 | สำหรับระบบปฏิบัติการที่ไม่สนับสนุนเหตุการณ์ไฟล์ตัวจับเวลานี้จะกำหนดว่าไฟล์บันทึกจะถูกสำรวจเพื่อการเปลี่ยนแปลงก่อนที่จะเก็บถาวรในมิลลิวินาที |
| การพัฒนา | |||
| development.debug | |||
scope | วัตถุ | การตั้งค่านี้กำหนดว่าขอบเขตใดที่บันทึกไว้ในเอาท์พุทการดีบักในโหมดการพัฒนา โดยค่าเริ่มต้นขอบเขตทั้งหมดจะถูกเปิดใช้งาน | |
depth | จำนวนเต็มบวก | 3 | เมื่อพลเมืองทิ้งวัตถุในเนื้อหาการดีบักจะตรวจสอบโดยใช้ Util.Inspect ของ Node การตั้งค่านี้กำหนดความลึกของการตรวจสอบซึ่งหมายถึงจำนวนโหนดที่จะตรวจสอบและแสดง ตัวเลขที่ใหญ่กว่าหมายถึงการตรวจสอบที่ลึกกว่าและประสิทธิภาพที่ช้าลง |
view | บูลีน | false | ตั้งค่านี้เป็นข้อมูลการดีบักที่แท้จริงเพื่อถ่ายโอนข้อมูลโดยตรงในมุมมอง HTML |
enableCache | บูลีน | false | โหมดการพัฒนาปิดใช้งานแคช เปลี่ยนการตั้งค่านี้เป็น true เพื่อเปิดใช้งานแคชในโหมดการพัฒนา |
| development.watcher | |||
custom | อาร์เรย์ | คุณสามารถบอกการเปลี่ยนโมดูลร้อนของ Citizen เพื่อดูโมดูลที่กำหนดเองของคุณเอง อาร์เรย์นี้สามารถมีวัตถุที่มี watch (เส้นทางไดเรกทอรีสัมพัทธ์ไปยังโมดูลของคุณภายในไดเรกทอรีแอพ) และ assign (ตัวแปรที่คุณกำหนดโมดูลเหล่านี้) คุณสมบัติ ตัวอย่าง:[ { "watch": "/util", "assign": "app.util" } ] | |
Citizen ใช้ Chokidar เป็นตัวเลือกไฟล์ดังนั้นตัวเลือก watcher สำหรับทั้งบันทึกและโหมดการพัฒนายังยอมรับตัวเลือกใด ๆ ที่ Chokidar อนุญาต
การตั้งค่าเหล่านี้จะเปิดเผยต่อสาธารณะผ่าน app.config.host และ app.config.citizen
เอกสารนี้ถือว่าชื่อตัวแปรแอพทั่วโลกของคุณคือ app ปรับตาม
app.start() | เริ่มต้นเซิร์ฟเวอร์แอปพลิเคชันเว็บพลเมือง |
app.config | การตั้งค่าการกำหนดค่าที่คุณจัดหาเมื่อเริ่มต้น การตั้งค่าของพลเมืองอยู่ใน app.config.citizen |
app.controllersapp.modelsapp.views | ไม่น่าเป็นไปได้ที่คุณจะต้องเข้าถึงคอนโทรลเลอร์และมุมมองโดยตรง แต่การอ้างอิง app.models แทนที่จะนำเข้าโมเดลของคุณได้รับประโยชน์ด้วยตนเองจากการเปลี่ยนโมดูลร้อนในตัวของพลเมือง |
app.helpers | โมดูลผู้ช่วย/ยูทิลิตี้ทั้งหมดที่อยู่ใน app/helpers/ นำเข้าสู่วัตถุผู้ช่วย |
app.cache.set()app.cache.get()app.cache.exists()app.cache.clear() | แอปพลิเคชันแคชและคีย์/ร้านค้าที่ใช้ภายในโดย Citizen พร้อมสำหรับแอปของคุณ |
app.log() | คอนโซลพื้นฐานและการบันทึกไฟล์ที่ใช้โดยพลเมืองส่งออกเพื่อการใช้งานของคุณ |
โครงสร้าง URL ของพลเมืองกำหนดว่าตัวควบคุมเส้นทางและการกระทำที่จะยิงผ่านพารามิเตอร์ URL และทำให้มีพื้นที่สำหรับเนื้อหาที่เป็นมิตรกับ SEO ซึ่งสามารถเพิ่มเป็นสองเท่าเป็นตัวระบุที่ไม่ซ้ำกัน โครงสร้างมีลักษณะเช่นนี้:
http://www.site.com/controller/seo-content/action/myAction/param/val/param2/val2
ตัวอย่างเช่นสมมติว่า URL พื้นฐานของเว็บไซต์ของคุณคือ:
http://www.cleverna.me
ตัวควบคุมเส้นทางเริ่มต้นคือ index และการดำเนินการเริ่มต้นคือ handler() ดังนั้นข้างต้นจึงเทียบเท่ากับสิ่งต่อไปนี้:
http://www.cleverna.me/index/action/handler
หากคุณมีตัวควบคุมเส้นทาง article คุณจะขอเช่นนี้:
http://www.cleverna.me/article
แทนที่จะเป็นสตริงแบบสอบถามพลเมืองผ่านพารามิเตอร์ URL ประกอบด้วยคู่ชื่อ/ค่า หากคุณต้องส่งรหัสบทความ 237 และหมายเลขหน้า 2 คุณจะต้องต่อท้ายชื่อ/ค่าคู่กับ URL:
http://www.cleverna.me/article/id/237/page/2
ชื่อพารามิเตอร์ที่ถูกต้องอาจมีตัวอักษรตัวเลขขีดล่างและขีดกลาง แต่ต้องเริ่มต้นด้วยตัวอักษรหรือขีดล่าง
การดำเนินการคอนโทรลเลอร์เริ่มต้นคือ handler() แต่คุณสามารถระบุการกระทำอื่นด้วยพารามิเตอร์ action (เพิ่มเติมในภายหลังนี้):
http://www.cleverna.me/article/action/edit
Citizen ยังช่วยให้คุณสามารถแทรกเนื้อหาที่เกี่ยวข้องลงใน URL ของคุณได้เช่นกัน:
http://www.cleverna.me/article/My-Clever-Article-Title/page/2
เนื้อหา SEO นี้จะต้องติดตามชื่อคอนโทรลเลอร์และนำหน้าคู่ชื่อ/ค่าใด ๆ รวมถึงการดำเนินการคอนโทรลเลอร์ คุณสามารถเข้าถึงได้โดยทั่วไปผ่าน route.descriptor หรือภายในขอบเขต url ( url.article ในกรณีนี้) ซึ่งหมายความว่าคุณสามารถใช้เป็นตัวระบุที่ไม่ซ้ำกัน (เพิ่มเติมเกี่ยวกับพารามิเตอร์ URL ในส่วนตัวควบคุมเส้นทาง)
action พารามิเตอร์ URL และ direct ถูกสงวนไว้สำหรับเฟรมเวิร์กดังนั้นอย่าใช้สำหรับแอปของคุณ
Citizen อาศัยการประชุมแบบจำลองมุมมองแบบง่าย ๆ รูปแบบบทความที่กล่าวถึงข้างต้นอาจใช้โครงสร้างต่อไปนี้:
app/
controllers/
routes/
article.js
models/
article.js // Optional, name it whatever you want
views/
article.html // The default view file name should match the controller name
ต้องมีตัวควบคุมเส้นทางอย่างน้อยหนึ่งตัวสำหรับ URL ที่กำหนดและไฟล์มุมมองเริ่มต้นของตัวควบคุมเส้นทางจะต้องแชร์ชื่อ รุ่นเป็นทางเลือก
มุมมองทั้งหมดสำหรับคอนโทรลเลอร์เส้นทางที่กำหนดสามารถมีอยู่ใน app/views/ ไดเรกทอรีหรือสามารถวางไว้ในไดเรกทอรีที่มีชื่อตรงกับของคอนโทรลเลอร์สำหรับองค์กรที่สะอาดกว่า:
app/
controllers/
routes/
article.js
models/
article.js
views/
article/
article.html // The default view
edit.html // Alternate article views
delete.html
เพิ่มเติมเกี่ยวกับมุมมองในส่วนมุมมอง
แบบจำลองและมุมมองเป็นทางเลือกและไม่จำเป็นต้องเชื่อมโยงกับคอนโทรลเลอร์เฉพาะ หากคอนโทรลเลอร์เส้นทางของคุณกำลังจะส่งผลผลิตไปยังคอนโทรลเลอร์อื่นเพื่อการประมวลผลเพิ่มเติมและการแสดงผลขั้นสุดท้ายคุณไม่จำเป็นต้องรวมมุมมองที่ตรงกัน (ดูตัวควบคุม Next Directive)
ตัวควบคุมเส้นทางพลเมืองเป็นเพียงโมดูล JavaScript ตัวควบคุมเส้นทางแต่ละเส้นทางต้องมีการส่งออกอย่างน้อยหนึ่งครั้งเพื่อทำหน้าที่เป็นการดำเนินการสำหรับเส้นทางที่ร้องขอ การดำเนินการเริ่มต้นควรมีชื่อว่า handler() ซึ่งเรียกว่าประชาชนเมื่อไม่ได้ระบุการดำเนินการใน URL
// Default route controller action
export const handler = async ( params , request , response , context ) => {
// Do some stuff
return {
// Send content and directives to the server
}
} Citizen Server Calling handler() หลังจากประมวลผลคำขอเริ่มต้นและส่งผ่านอาร์กิวเมนต์ 4: วัตถุ params ที่มีพารามิเตอร์ของคำขอ, request Node.js และวัตถุ response และบริบทของคำขอปัจจุบัน
params config | การกำหนดค่าแอปของคุณรวมถึงการปรับแต่งใด ๆ สำหรับการดำเนินการคอนโทรลเลอร์ปัจจุบัน |
route | รายละเอียดของเส้นทางที่ร้องขอเช่น URL และชื่อของตัวควบคุมเส้นทาง |
url | พารามิเตอร์ใด ๆ ที่ได้จาก URL |
form | ข้อมูลที่รวบรวมจากโพสต์ |
payload | น้ำหนักบรรทุกคำขอดิบ |
cookie | คุกกี้ที่ส่งพร้อมคำขอ |
session | ตัวแปรเซสชันหากเปิดใช้งานเซสชัน |
นอกเหนือจากการเข้าถึงวัตถุเหล่านี้ภายในคอนโทรลเลอร์ของคุณแล้วยังรวมอยู่ในบริบทมุมมองของคุณโดยอัตโนมัติเพื่อให้คุณสามารถอ้างอิงได้ภายในเทมเพลตมุมมองของคุณเป็นตัวแปรท้องถิ่น (รายละเอียดเพิ่มเติมในส่วนมุมมอง)
ตัวอย่างเช่นตาม URL บทความก่อนหน้า ...
http://www.cleverna.me/article/My-Clever-Article-Title/id/237/page/2
... คุณจะมี params.url ต่อไปนี้วัตถุที่ผ่านไปยังคอนโทรลเลอร์ของคุณ:
{
article : 'My-Clever-Article-Title' ,
id : '237' ,
page : '2'
} ชื่อคอนโทรลเลอร์กลายเป็นคุณสมบัติในขอบเขต URL ที่อ้างอิงตัวบ่งชี้ซึ่งทำให้เหมาะสำหรับใช้เป็นตัวระบุที่ไม่ซ้ำกัน นอกจากนี้ยังมีอยู่ในวัตถุ params.route เป็น params.route.descriptor
อาร์กิวเมนต์ context มีข้อมูลหรือคำสั่งใด ๆ ที่ถูกสร้างขึ้นโดยตัวควบคุมก่อนหน้านี้ในห่วงโซ่โดยใช้คำสั่ง return ของพวกเขา
ในการส่งคืนผลลัพธ์ของการกระทำของคอนโทรลเลอร์ให้รวมคำสั่ง return พร้อมข้อมูลและคำสั่งใด ๆ ที่คุณต้องการส่งต่อไปยังพลเมือง
การใช้พารามิเตอร์ URL ข้างต้นฉันสามารถดึงเนื้อหาบทความจากโมเดลและส่งกลับไปยังเซิร์ฟเวอร์:
// article controller
export const handler = async ( params ) => {
// Get the article
const article = await app . models . article . get ( {
article : params . url . article ,
page : params . url . page
} )
const author = await app . models . article . getAuthor ( {
author : article . author
} )
// Any data you want available to the view should be placed in the local directive
return {
local : {
article : article ,
author : author
}
}
} การดำเนินการสำรองสามารถร้องขอได้โดยใช้พารามิเตอร์ action URL ตัวอย่างเช่นเราอาจต้องการการกระทำที่แตกต่างและดูเพื่อแก้ไขบทความ:
// http://www.cleverna.me/article/My-Clever-Article-Title/id/237/page/2/action/edit
// article controller
export const handler = async ( params ) => {
// Get the article
const article = await app . models . article . get ( {
article : params . url . article ,
page : params . url . page
} )
const author = await app . models . article . getAuthor ( {
author : article . author
} )
// Return the article for view rendering using the local directive
return {
local : {
article : article ,
author : author
}
}
}
export const edit = async ( params ) => {
// Get the article
const article = await app . models . article . get ( {
article : params . url . article ,
page : params . url . page
} )
// Use the /views/article/edit.html view for this action
return {
local : {
article : article
} ,
view : 'edit'
}
} คุณวางข้อมูลใด ๆ ที่คุณต้องการส่งกลับไปยัง Citizen ภายในแถลงการณ์ return ข้อมูลทั้งหมดที่คุณต้องการแสดงในมุมมองของคุณควรส่งผ่านไปยังพลเมืองภายในวัตถุที่เรียกว่า local ดังที่แสดงด้านบน วัตถุเพิ่มเติมสามารถส่งผ่านไปยังพลเมืองเพื่อกำหนดคำสั่งที่ให้คำแนะนำกับเซิร์ฟเวอร์ (ดูคำสั่งคอนโทรลเลอร์) คุณสามารถเพิ่มวัตถุของคุณเองในบริบทและส่งผ่านจากคอนโทรลเลอร์ไปยังคอนโทรลเลอร์ (เพิ่มเติมในส่วนการผูกมัดคอนโทรลเลอร์)
โมเดลเป็นโมดูลเสริมและโครงสร้างของพวกเขาขึ้นอยู่กับคุณอย่างสมบูรณ์ พลเมืองไม่ได้พูดคุยกับโมเดลของคุณโดยตรง มันเก็บไว้เฉพาะใน app.models เพื่อความสะดวกของคุณ นอกจากนี้คุณยังสามารถนำเข้าพวกเขาด้วยตนเองในคอนโทรลเลอร์ของคุณหากคุณต้องการ
ฟังก์ชั่นต่อไปนี้เมื่อวางใน app/models/article.js จะสามารถเข้าถึงได้ในแอพของคุณผ่าน app.models.article.get() :
// app.models.article.get()
export const get = async ( id ) => {
let article = // do some stuff to retrieve the article from the db using the provided ID, then...
return article
} พลเมืองใช้ตัวอักษรเทมเพลตเพื่อดูการแสดงผลตามค่าเริ่มต้น คุณสามารถติดตั้ง casolidate.js และใช้เอ็นจิ้นเทมเพลตที่รองรับใด ๆ เพียงอัปเดตการตั้งค่าการกำหนดค่า templateEngine ตามลำดับ
ใน article.html คุณสามารถอ้างอิงตัวแปรที่คุณวางไว้ในวัตถุ local ที่ส่งผ่านลงในคำสั่งการส่งคืนของคอนโทรลเลอร์เส้นทาง พลเมืองยังฉีดคุณสมบัติจากวัตถุ params ลงในบริบทมุมมองของคุณโดยอัตโนมัติดังนั้นคุณจึงสามารถเข้าถึงวัตถุเหล่านั้นเป็นตัวแปรท้องถิ่น (เช่นขอบเขต url ):
<!-- article.html -->
<!doctype html >
< html >
< body >
< main >
< h1 >
${local.article.title} — Page ${url.page}
</ h1 >
< h2 > ${local.author.name}, ${local.article.published} </ h2 >
< p >
${local.article.summary}
</ p >
< section >
${local.article.text}
</ section >
</ main >
</ body >
</ html > โดยค่าเริ่มต้นเซิร์ฟเวอร์จะแสดงมุมมองที่มีชื่อตรงกับของคอนโทรลเลอร์ ในการแสดงมุมมองที่แตกต่างให้ใช้คำสั่ง view ในคำสั่งการส่งคืนของคุณ
มุมมองทั้งหมดไปใน /app/views หากคอนโทรลเลอร์มีหลายมุมมองคุณสามารถจัดระเบียบได้ภายในไดเรกทอรีที่ตั้งชื่อตามคอนโทรลเลอร์นั้น
app/
controllers/
routes/
article.js
index.js
views/
article/
article.html // Default article controller view
edit.html
index.html // Default index controller view
คุณสามารถบอกตัวควบคุมเส้นทางเพื่อส่งคืนตัวแปรท้องถิ่นเป็น JSON หรือ JSON-P โดยการตั้งค่า HTTP Accept ที่เหมาะสมในคำขอของคุณปล่อยให้ทรัพยากรเดียวกันให้บริการทั้งมุมมอง HTML ที่สมบูรณ์และ JSON สำหรับคำขอ AJAX และ APIs
การกระทำของบทความ Route Controller handler() จะกลับมา:
{
"article" : {
"title" : " My Clever Article Title " ,
"summary" : " Am I not terribly clever? " ,
"text" : " This is my article text. "
},
"author" : {
"name" : " John Smith " ,
"email" : " [email protected] "
}
} ไม่ว่าคุณจะเพิ่มอะไรในคำสั่งการส่งคืนของคอนโทรลเลอร์วัตถุ local จะถูกส่งคืน
สำหรับ JSONP ใช้ callback ใน URL:
http://www.cleverna.me/article/My-Clever-Article-Title/callback/foo
ผลตอบแทน:
foo ( {
"article" : {
"title" : "My Clever Article Title" ,
"summary" : "Am I not terribly clever?" ,
"text" : "This is my article text."
} ,
"author" : {
"name" : "John Smith" ,
"email" : "[email protected]"
}
} ) ; หากต้องการบังคับประเภทเนื้อหาเฉพาะสำหรับคำขอที่กำหนดให้ตั้งค่า response.contentType
export const handler = async ( params , request , response ) => {
// Every request will receive a JSON response regardless of the Accept header
response . contentType = 'application/json'
}คุณสามารถบังคับประเภทการตอบกลับทั่วโลกในทุกคำขอภายในเบ็ดเหตุการณ์
ผู้ช่วยเป็นโมดูลยูทิลิตี้เสริมและโครงสร้างของพวกเขาขึ้นอยู่กับคุณอย่างสมบูรณ์ พวกเขาเก็บไว้ใน app.helpers เพื่อความสะดวกของคุณ นอกจากนี้คุณยังสามารถนำเข้าด้วยตนเองไปยังคอนโทรลเลอร์และรุ่นของคุณหากต้องการ
ฟังก์ชั่นต่อไปนี้เมื่อวางใน app/helpers/validate.js จะสามารถเข้าถึงได้ในแอพของคุณผ่าน app.helpers.validate.email() :
// app.helpers.validate.email()
export const email = ( address ) => {
const emailRegex = new RegExp ( / [a-z0-9!##$%&''*+/=?^_`{|}~-]+(?:.[a-z0-9!##$%&''*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])? / i )
return emailRegex . test ( address )
} Citizen เก็บโมดูลทั้งหมดในขอบเขต app ไม่เพียง แต่เพื่อการดึงข้อมูลที่ง่าย แต่เพื่อรองรับการเปลี่ยนโมดูลร้อน (HMR) เมื่อคุณบันทึกการเปลี่ยนแปลงโมดูลหรือมุมมองใด ๆ ในโหมดการพัฒนาพลเมืองจะล้างการนำเข้าโมดูลที่มีอยู่และนำเข้ามาอีกครั้งโมดูลนั้นในเวลาจริง
คุณจะเห็นบันทึกคอนโซลที่สังเกตเห็นไฟล์ที่ได้รับผลกระทบและแอปของคุณจะทำงานต่อไป ไม่จำเป็นต้องรีสตาร์ท
พลเมืองพยายามอย่างเต็มที่ที่จะจัดการกับข้อผิดพลาดอย่างสง่างามโดยไม่ต้องออกจากกระบวนการ การกระทำของคอนโทรลเลอร์ต่อไปนี้จะทำให้เกิดข้อผิดพลาด แต่เซิร์ฟเวอร์จะตอบกลับด้วย 500 และทำงานต่อไป:
export const handler = async ( params ) => {
// app.models.article.foo() doesn't exist, so this action will throw an error
const foo = await app . models . article . foo ( params . url . article )
return {
local : foo
}
}นอกจากนี้คุณยังสามารถโยนข้อผิดพลาดด้วยตนเองและปรับแต่งข้อความแสดงข้อผิดพลาด:
export const handler = async ( params ) => {
// Get the article
const article = await app . models . article . get ( {
article : params . url . article ,
page : params . url . page
} )
// If the article exists, return it
if ( article ) {
return {
local : {
article : article
}
}
// If the article doesn't exist, throw a 404
} else {
// Error messages default to the standard HTTP Status Code response, but you can customize them.
let err = new Error ( 'The requested article does not exist.' )
// The HTTP status code defaults to 500, but you can specify your own
err . statusCode = 404
throw err
}
} โปรดทราบว่า params.route.controller ได้รับการอัปเดตจากคอนโทรลเลอร์ที่ร้องขอไปจนถึง error ดังนั้นการอ้างอิงใด ๆ ในแอพของคุณไปยังคอนโทรลเลอร์ที่ร้องขอควรคำนึงถึงสิ่งนี้
ข้อผิดพลาดจะถูกส่งคืนในรูปแบบที่ร้องขอโดยเส้นทาง หากคุณขอ JSON และเส้นทางจะเกิดข้อผิดพลาดพลเมืองจะส่งคืนข้อผิดพลาดในรูปแบบ JSON
โครงกระดูกแอพที่สร้างขึ้นโดยยูทิลิตี้ Scaffold รวมถึงเทมเพลตมุมมองข้อผิดพลาดเพิ่มเติมสำหรับข้อผิดพลาดไคลเอนต์และเซิร์ฟเวอร์ทั่วไป แต่คุณสามารถสร้างเทมเพลตสำหรับรหัสข้อผิดพลาด HTTP ใด ๆ
วิธีการจัดการข้อผิดพลาดเริ่มต้นของพลเมืองคือ capture ซึ่งพยายามกู้คืนอย่างสง่างาม หากคุณต้องการออกจากกระบวนการหลังจากเกิดข้อผิดพลาดให้เปลี่ยน config.citizen.errors เพื่อ exit
// config file: exit the process after an error
{
"citizen" : {
"errors" : "exit"
}
}หลังจากการยิงตัวจัดการข้อผิดพลาดของแอปพลิเคชันพลเมืองจะออกจากกระบวนการ
ในการสร้างมุมมองข้อผิดพลาดที่กำหนดเองสำหรับข้อผิดพลาดของเซิร์ฟเวอร์ให้สร้างไดเรกทอรีที่เรียกว่า /app/views/error และเติมด้วยเทมเพลตที่ตั้งชื่อตามรหัสตอบกลับ HTTP หรือรหัสข้อผิดพลาดโหนด
app/
views/
error/
500.html // Displays any 500-level error
404.html // Displays 404 errors specifically
ENOENT.html // Displays bad file read operations
error.html // Displays any error without its own template
นอกเหนือจากการดูข้อมูลแล้วคำสั่งการส่งคืนของ Route Controller ยังสามารถผ่านคำสั่งเพื่อแสดงมุมมองทางเลือกตั้งค่าคุกกี้และตัวแปรเซสชันเริ่มต้นการเปลี่ยนเส้นทางการโทรและการแสดงผลรวมถึงการกระทำของตัวควบคุมเส้นทางแคช/มุมมอง (หรือคำขอทั้งหมด) และส่งคำขอไปยังคอนโทรลเลอร์อื่นสำหรับการประมวลผลเพิ่มเติม
โดยค่าเริ่มต้นเซิร์ฟเวอร์จะแสดงมุมมองที่มีชื่อตรงกับของคอนโทรลเลอร์ ในการแสดงมุมมองที่แตกต่างให้ใช้คำสั่งมุม view ในคำสั่งคืนของคุณ:
// article controller
export const edit = async ( params ) => {
const article = await app . models . article . get ( {
article : params . url . article ,
page : params . url . page
} )
return {
local : article ,
// This tells the server to render app/views/article/edit.html
view : 'edit'
}
} คุณตั้งค่าคุกกี้โดยส่งคืนวัตถุ cookie ภายในการกระทำของคอนโทรลเลอร์
export const handler = async ( params ) => {
return {
cookie : {
// Cookie shorthand sets a cookie called username using the default cookie properties
username : params . form . username ,
// Sets a cookie called last_active that expires in 20 minutes
last_active : {
value : new Date ( ) . toISOString ( ) ,
expires : 20
}
}
}
}นี่คือตัวอย่างของการตั้งค่าเริ่มต้นของวัตถุคุกกี้ที่สมบูรณ์:
myCookie = {
value : 'myValue' ,
// Valid expiration options are:
// 'now' - deletes an existing cookie
// 'never' - current time plus 30 years, so effectively never
// 'session' - expires at the end of the browser session (default)
// [time in minutes] - expires this many minutes from now
expires : 'session' ,
path : '/' ,
// citizen's cookies are accessible via HTTP/HTTPS only by default. To access a
// cookie via JavaScript, set this to false.
httpOnly : true ,
// Cookies are insecure when set over HTTP and secure when set over HTTPS.
// You can override that behavior globally with the https.secureCookies setting
// in your config or on a case-by-case basis with this setting.
secure : false
} cookie ตั้งค่าคุกกี้ params.cookie ไคลเอน
<!doctype html >
< html >
< body >
< section >
Welcome, ${cookie.username}.
</ section >
</ body >
</ html > ตัวแปรคุกกี้ที่คุณตั้งค่าภายในคอนโทรลเลอร์ของคุณจะไม่สามารถใช้งานได้ทันทีภายใน params.cookie ขอบเขต พลเมืองจะต้องได้รับบริบทจากคอนโทรลเลอร์และส่งการตอบกลับไปยังลูกค้าก่อนดังนั้นให้ใช้อินสแตนซ์ท้องถิ่นของตัวแปรหากคุณต้องการเข้าถึงในระหว่างการร้องขอเดียวกัน
คุกกี้ทั้งหมดที่กำหนดโดยพลเมืองเริ่มต้นด้วยคำนำหน้า ctzn_ เพื่อหลีกเลี่ยงการชน อย่าเริ่มชื่อคุกกี้ของคุณด้วย ctzn_ และคุณไม่ควรมีปัญหา
หากคุณใช้พลเมืองที่อยู่เบื้องหลังพร็อกซีเช่น Nginx หรือ Apache ตรวจสอบให้แน่ใจว่าคุณมีส่วนหัว Forwarded HTTP ในการกำหนดค่าเซิร์ฟเวอร์ของคุณเพื่อให้การจัดการคุกกี้ที่ปลอดภัยของพลเมืองทำงานได้อย่างถูกต้อง
นี่คือตัวอย่างของวิธีการตั้งค่านี้ใน Nginx:
location / {
proxy_set_header Forwarded "for=$remote_addr;host=$host;proto=$scheme;";
proxy_pass http://127.0.0.1:8080;
}
หากเปิดใช้งานเซสชันคุณสามารถเข้าถึงตัวแปรเซสชันผ่าน params.session ในคอนโทรลเลอร์ของคุณหรือเพียงแค่ session ภายในมุมมอง ขอบเขตท้องถิ่นเหล่านี้อ้างอิงเซสชันของผู้ใช้ปัจจุบันโดยไม่ต้องผ่านรหัสเซสชัน
โดยค่าเริ่มต้นเซสชันมีคุณสมบัติสี่ประการ: id started expires และ timer รหัสเซสชันจะถูกส่งไปยังไคลเอนต์เป็นคุกกี้ที่เรียกว่า ctzn_session_id
การตั้งค่าตัวแปรเซสชันนั้นค่อนข้างเหมือนกับการตั้งค่าตัวแปรคุกกี้:
return {
session : {
username : 'Danny' ,
nickname : 'Doc'
}
} เช่นเดียวกับคุกกี้ตัวแปรเซสชันที่คุณเพิ่งได้รับมอบหมายไม่สามารถใช้ได้ในระหว่างการร้องขอเดียวกันภายในขอบเขต params.session ดังนั้นให้ใช้อินสแตนซ์ท้องถิ่นหากคุณต้องการเข้าถึงข้อมูลนี้ทันที
เซสชั่นหมดอายุตามคุณสมบัติการกำหนดค่าของ sessions.lifespan ซึ่งแสดงถึงความยาวของเซสชันในไม่กี่นาที ค่าเริ่มต้นคือ 20 นาที timer จะถูกรีเซ็ตด้วยแต่ละคำขอจากผู้ใช้ เมื่อ timer หมดเซสชันจะถูกลบ คำขอไคลเอนต์ใด ๆ หลังจากเวลานั้นจะสร้างรหัสเซสชันใหม่และส่งคุกกี้เซสชัน ID ใหม่ไปยังลูกค้า
เพื่อบังคับให้ชัดเจนและหมดอายุเซสชันของผู้ใช้ปัจจุบัน:
return {
session : {
expires : 'now'
}
} ตัวแปรเซสชันทั้งหมดที่กำหนดโดย Citizen เริ่มต้นด้วยคำนำหน้า ctzn_ เพื่อหลีกเลี่ยงการชน อย่าเริ่มชื่อตัวแปรเซสชันของคุณด้วย ctzn_ และคุณไม่ควรมีปัญหา
คุณสามารถผ่านคำแนะนำการเปลี่ยนเส้นทางไปยังเซิร์ฟเวอร์ที่จะเริ่มต้นหลังจากดำเนินการคอนโทรลเลอร์
วัตถุ redirect ใช้สตริง URL ในเวอร์ชันชวเลขหรือสามตัวเลือก: statusCode , url และ refresh หากคุณไม่ได้ให้รหัสสถานะพลเมืองใช้ 302 (เปลี่ยนเส้นทางชั่วคราว) ตัวเลือก refresh กำหนดว่าการเปลี่ยนเส้นทางใช้ส่วนหัวตำแหน่งหรือส่วนหัวรีเฟรชที่ไม่ได้มาตรฐาน
// Initiate a temporary redirect using the Location header
return {
redirect : '/login'
}
// Initiate a permanent redirect using the Refresh header, delaying the redirect by 5 seconds
return {
redirect : {
url : '/new-url' ,
statusCode : 301 ,
refresh : 5
}
} ซึ่งแตกต่างจากส่วนหัวของตำแหน่งหากคุณใช้ตัวเลือก refresh พลเมืองจะส่งมุมมองที่แสดงผลไปยังลูกค้าเนื่องจากการเปลี่ยนเส้นทางเกิดขึ้นฝั่งไคลเอ็นต์
การใช้ส่วนหัวของสถานที่ตั้ง (ในความคิดของฉัน) ส่วนหัวผู้อ้างอิงเนื่องจากผู้อ้างอิงจบลงด้วยการเป็นทรัพยากรที่เริ่มต้นการเปลี่ยนเส้นทาง แต่ทรัพยากรก่อนหน้าเว็บที่เริ่มต้น เพื่อแก้ไขปัญหานี้พลเมืองเก็บตัวแปรเซสชันที่เรียกว่า ctzn_referer ที่มี URL ของทรัพยากรที่เริ่มต้นการเปลี่ยนเส้นทางซึ่งคุณสามารถใช้เพื่อเปลี่ยนเส้นทางผู้ใช้อย่างถูกต้อง ตัวอย่างเช่นหากผู้ใช้ที่ไม่ผ่านการตรวจสอบความพยายามในการเข้าถึงหน้าปลอดภัยและคุณเปลี่ยนเส้นทางไปยังแบบฟอร์มการเข้าสู่ระบบที่อยู่ของหน้าความปลอดภัยจะถูกเก็บไว้ใน ctzn_referer เพื่อให้คุณสามารถส่งพวกเขาไปที่นั่นแทนหน้าก่อนหน้า
หากคุณยังไม่ได้เปิดใช้งานเซสชัน Citizen จะกลับไปสร้างคุกกี้ชื่อ ctzn_referer แทน
หากคุณใช้พลเมืองที่อยู่เบื้องหลังพร็อกซีเช่น Nginx หรือ Apache ตรวจสอบให้แน่ใจว่าคุณมีส่วนหัว Forwarded HTTP ในการกำหนดค่าเซิร์ฟเวอร์ของคุณดังนั้น ctzn_referer ทำงานได้อย่างถูกต้อง
นี่คือตัวอย่างของวิธีการตั้งค่านี้ใน Nginx:
location / {
proxy_set_header Forwarded "for=$remote_addr;host=$host;proto=$scheme;";
proxy_pass http://127.0.0.1:8080;
}
คุณสามารถตั้งค่าส่วนหัว HTTP โดยใช้คำสั่ง header :
return {
header : {
'Cache-Control' : 'max-age=86400' ,
'Date' : new Date ( ) . toISOString ( )
}
} นอกจากนี้คุณยังสามารถตั้งค่าส่วนหัวได้โดยตรงโดยใช้วิธี response.setHeader() แต่การใช้คำสั่ง header ของ Citizen รักษาส่วนหัวเหล่านั้นไว้ในแคชคำขอดังนั้นพวกเขาจะถูกนำไปใช้เมื่อใดก็ตามที่การกระทำของคอนโทรลเลอร์ถูกดึงออกมาจากแคช
Citizen ช่วยให้คุณใช้รูปแบบ MVC ที่สมบูรณ์แบบซึ่งเป็นส่วนประกอบของ Citizen แต่ละตัวมีตัวควบคุมเส้นทางโมเดลและมุมมองของตัวเอง รวมถึงสามารถใช้ในการดำเนินการหรือส่งคืนมุมมองการแสดงผลที่สมบูรณ์ ตัวควบคุมเส้นทางใด ๆ สามารถรวม
สมมติว่าเทมเพลตรูปแบบของบทความของเรามีเนื้อหาต่อไปนี้ ส่วนหัวมีข้อมูลเมตาแบบไดนามิกและเนื้อหาของส่วนหัวจะเปลี่ยนไปขึ้นอยู่กับว่าผู้ใช้เข้าสู่ระบบหรือไม่:
<!doctype html >
< html >
< head >
< title > ${local.metaData.title} </ title >
< meta name =" description " content =" ${local.metaData.description} " >
< meta name =" keywords " content =" ${local.metaData.keywords} " >
< link rel =" stylesheet " type =" text/css " href =" site.css " >
</ head >
< body >
< header >
${ cookie.username ? ' < p > Welcome, ' + cookie.username + ' </ p > ' : ' < a href =" /login " > Login </ a > ' }
</ header >
< main >
< h1 > ${local.article.title} — Page ${url.page} </ h1 >
< p > ${local.article.summary} </ p >
< section > ${local.article.text} </ section >
</ main >
</ body >
</ html >มันอาจเหมาะสมที่จะใช้รวมถึงส่วนหัวและส่วนหัวเพราะคุณจะใช้รหัสนั้นทุกที่ แต่แทนที่จะเป็นส่วนที่เรียบง่ายคุณสามารถสร้างพลเมืองรวมถึง ส่วนหัวสามารถใช้โมเดลของตัวเองสำหรับการเติมข้อมูลเมตาและเนื่องจากส่วนหัวแตกต่างกันสำหรับผู้ใช้ที่ได้รับการรับรองความถูกต้องให้ดึงตรรกะนั้นออกมาจากมุมมองและวางไว้ในคอนโทรลเลอร์ของส่วนหัว ฉันชอบที่จะติดตามการประชุมของการเริ่มต้นบางส่วนด้วยขีดเส้นใต้ แต่ก็ขึ้นอยู่กับคุณ:
app/
controllers/
routes/
_head.js
_header.js
article.js
models/
_head.js
article.js
views/
_head.html
_header/
_header.html
_header-authenticated.html // A different header for logged in users
article.html
เมื่อตัวควบคุมบทความถูกไล่ออกจะต้องบอกพลเมืองซึ่งรวมถึงความต้องการ เราทำเช่นนั้นกับคำสั่ง include :
// article controller
export const handler = async ( params ) => {
// Get the article
const article = await app . models . article . get ( {
article : params . url . article ,
page : params . url . page
} )
return {
local : {
article : article
} ,
include : {
// Include shorthand is a string containing the pathname to the desired route controller
_head : '/_head/action/article' ,
// Long-form include notation can explicitly define a route controller, action, and view
_header : {
controller : '_header' ,
// If the username cookie exists, use the authenticated action. If not, use the default action.
action : params . cookie . username ? 'authenticated' : 'handler'
}
}
}
} พลเมืองรวมถึงรูปแบบมีข้อกำหนดเช่นเดียวกับรูปแบบปกติรวมถึงคอนโทรลเลอร์ที่มีการกระทำสาธารณะ คำสั่ง include บนบอกให้พลเมืองเรียกตัวควบคุม _head และ _header ส่งอาร์กิวเมนต์เดียวกันที่ส่งผ่านไปยังตัวควบคุม article (พารามิเตอร์การร้องขอการตอบสนองบริบท) แสดงมุมมองที่เกี่ยวข้องและเพิ่มมุมมองผลลัพธ์ในบริบทมุมมอง
นี่คือสิ่งที่คอนโทรลเลอร์ส่วนหัวของเราอาจมีลักษณะ:
// _head controller
export const article = async ( params ) => {
let metaData = await app . models . _head ( { article : params . url . article } )
return {
local : {
metaData : metaData
}
}
}และมุมมองส่วนหัว:
< head >
< title > ${local.metaData.title} </ title >
< meta name =" description " content =" ${local.metaData.description} " >
< meta name =" keywords " content =" ${local.metaData.keywords} " >
< link rel =" stylesheet " type =" text/css " href =" site.css " >
</ head >นี่คือสิ่งที่ตัวควบคุมส่วนหัวของเราอาจมีลักษณะ:
// _header controller
// No need for a return statement, and no need to specify the view
// because handler() renders the default view.
//
// Every route controller needs at least one action, even if it's empty.
export const handler = ( ) => { }
export const authenticated = ( ) => {
return {
view : '_header-authenticated'
}
}และมุมมองส่วนหัว:
<!-- /views/_header/_header.html -->
< header >
< a href =" /login " > Login </ a >
</ header > <!-- /views/_header/_header-authenticated.html -->
< header >
< p > Welcome, ${cookie.username} </ p >
</ header > การแสดงผลรวมจะถูกเก็บไว้ในขอบเขต include :
<!-- /views/article.html -->
<!doctype html >
< html >
${include._head}
< body >
${include._header}
< main >
< h1 > ${local.title} — Page ${url.page} </ h1 >
< p > ${local.summary} </ p >
< section > ${local.text} </ section >
</ main >
</ body >
</ html >พลเมืองรวมถึงมีอยู่ในตัวเองและส่งไปยังคอนโทรลเลอร์การโทรเป็นมุมมองที่แสดงผลอย่างสมบูรณ์ ในขณะที่พวกเขาได้รับข้อมูลเดียวกัน (พารามิเตอร์ URL, อินพุตแบบฟอร์ม, บริบทการร้องขอ ฯลฯ ) เป็นตัวควบคุมการโทรข้อมูลที่สร้างขึ้นภายใน ANCTICT จะไม่ถูกส่งกลับไปยังผู้โทร
รูปแบบหมายถึงการใช้เป็นตัวรวมสามารถเข้าถึงได้ผ่าน HTTP เช่นเดียวกับตัวควบคุมเส้นทางอื่น ๆ คุณสามารถขอคอนโทรลเลอร์ _header ได้เช่น So และรับ HTML หรือ JSON เป็นคำตอบ:
http://cleverna.me/_header
นี่เป็นสิ่งที่ยอดเยี่ยมสำหรับการจัดการด้านเซิร์ฟเวอร์คำขอแรกจากนั้นอัปเดตเนื้อหาด้วยไลบรารีฝั่งไคลเอ็นต์
พลเมืองรวมถึงการให้ฟังก์ชั่นที่หลากหลาย แต่พวกเขามีข้อ จำกัด และอาจเกินความจริงในบางสถานการณ์
Citizen ช่วยให้คุณสามารถห่วงโซ่ควบคุมเส้นทางหลายเส้นทางเข้าด้วยกันในซีรีส์จากคำขอเดียวโดยใช้คำสั่ง next คอนโทรลเลอร์ที่ร้องขอผ่านข้อมูลและแสดงมุมมองไปยังคอนโทรลเลอร์ที่ตามมาเพิ่มข้อมูลของตัวเองและแสดงมุมมองของตัวเอง
คุณสามารถสตริงคอนโทรลเลอร์เส้นทางจำนวนมากเข้าด้วยกันในคำขอเดียวตามที่คุณต้องการ Each route controller will have its data and view output stored in the params.route.chain object.
// The index controller accepts the initial request and hands off execution to the article controller
export const handler = async ( params ) => {
let user = await app . models . user . getUser ( { userID : params . url . userID } )
return {
local : {
user : user
} ,
// Shorthand for next is a string containing the pathname to the route controller.
// URL paramaters in this route will be parsed and handed to the next controller.
next : '/article/My-Article/id/5'
// Or, you can be explicit, but without parameters
next : {
// Pass this request to app/controllers/routes/article.js
controller : 'article' ,
// Specifying the action is optional. The next controller will use its default action, handler(), unless you specify a different action here.
action : 'handler' ,
// Specifying the view is optional. The next controller will use its default view unless you tell it to use a different one.
view : 'article'
}
// You can also pass custom directives and data.
doSomething: true
}
}Each controller in the chain has access to the previous controller's context and views. The last controller in the chain provides the final rendered view. A layout controller with all your site's global elements is a common use for this.
// The article controller does its thing, then hands off execution to the _layout controller
export const handler = async ( params , request , response , context ) => {
let article = await getArticle ( { id : params . url . id } )
// The context from the previous controller is available to you in the current controller.
if ( context . doSomething ) { // Or, params.route.chain.index.context
await doSomething ( )
}
return {
local : {
article : article
} ,
next : '/_layout'
}
} The rendered view of each controller in the chain is stored in the route.chain object:
<!-- index.html, which is stored in route.chain.index.output -->
< h1 > Welcome, ${local.user.username}! </ h1 >
<!-- article.html, which is stored in route.chain.article.output -->
< h1 > ${local.article.title} </ h1 >
< p > ${local.article.summary} </ p >
< section > ${local.article.text} </ section >The layout controller handles the includes and renders its own view. Because it's the last controller in the chain, this rendered view is what will be sent to the client.
// _layout controller
export const handler = async ( params ) => {
return {
include : {
_head : '/_head' ,
_header : {
controller : '_header' ,
action : params . cookie . username ? 'authenticated' : 'handler'
} ,
_footer : '/_footer
}
}
} <!-- _layout.html -->
<!doctype html >
< html >
${include._head}
< body >
${include._header}
< main >
<!-- You can render each controller's view explicitly -->
${route.chain.index.output}
${route.chain.article.output}
<!-- Or, you can loop over the route.chain object to output the view from each controller in the chain -->
${Object.keys(route.chain).map( controller = > { return route.chain[controller].output }).join('')}
</ main >
${include._footer}
</ body >
</ html > You can skip rendering a controller's view in the chain by setting the view directive to false:
// This controller action won't render a view
export const handler = async ( ) => {
return {
view : false ,
next : '/_layout'
}
} To bypass next in a request, add /direct/true to the URL.
http://cleverna.me/index/direct/true
The requested route controller's next directive will be ignored and its view will be returned to the client directly.
As mentioned in the config section at the beginning of this document, you can specify a default layout controller in your config so you don't have to insert it at the end of every controller chain:
{
"citizen" : {
"layout" : {
"controller" : " _layout " ,
"view" : " _layout "
}
}
} If you use this method, there's no need to use next for the layout. The last controller in the chain will always hand the request to the layout controller for final rendering.
citizen provides several ways for you to improve your app's performance, most of which come at the cost of system resources (memory or CPU).
In many cases, a requested URL or route controller action will generate the same view every time based on the same input parameters, so it doesn't make sense to run the controller chain and render the view from scratch for each request. citizen provides flexible caching capabilities to speed up your server side rendering via the cache directive.
If a given request (URL) will result in the exact same rendered view with every request, you can cache that request with the request property. This is the fastest cache option because it pulls a fully rendered view from memory and skips all controller processing.
Let's say you chain the index, article, and layout controllers like we did above. If you put the following cache directive in your index controller, the requested URL's response will be cached and subsequent requests will skip the index, article, and layout controllers entirely.
return {
next : '/article' ,
cache : {
request : true
}
}For the request cache directive to work, it must be placed in the first controller in the chain; in other words, the original requested route controller (index in this case). It will be ignored in any subsequent controllers.
The URL serves as the cache key, so each of the following URLs would generate its own cache item:
http://cleverna.me/article
http://cleverna.me/article/My-Article
http://cleverna.me/article/My-Article/page/2
The example above is shorthand for default cache settings. The cache.request directive can also be an object with options:
// Cache the requested route with some additional options
return {
cache : {
request : {
// Optional. This setting lets the server respond with a 304 Not Modified
// status if the cache content hasn't been updated since the client last
// accessed the route. Defaults to the current time if not specified.
lastModified : new Date ( ) . toISOString ( ) ,
// Optional. List of valid URL parameters that protects against accidental
// caching of malformed URLs.
urlParams : [ 'article' , 'page' ] ,
// Optional. Life of cached item in minutes. Default is 15 minutes.
// For no expiration, set to 'application'.
lifespan : 15 ,
// Optional. Reset the cached item's expiration timer whenever the item is
// accessed, keeping it in the cache until traffic subsides. Default is true.
resetOnAccess : true
}
}
} If a given route chain will vary across requests, you can still cache individual controller actions to speed up rendering using the action property.
// Cache this controller action using the default settings
return {
cache : {
action : true
}
}
// Cache this controller with additional options
return {
cache : {
action : {
// These options function the same as request caching (see above)
urlParams : [ 'article' , 'page' ] ,
lifespan : 15 ,
resetOnAccess : true
}
}
}When you cache controller actions, their context is also cached. Setting a cookie or session variable in a cached controller action means all future requests for that action will set the same cookie or session variable—probably not something you want to do with user data.
lastModified This setting lets the server respond with a faster 304 Not Modified response if the content of the request cache hasn't changed since the client last accessed it. By default, it's set to the time at which the request was cached, but you can specify a custom date in ISO format that reflects the last modification to the request's content.
return {
next : '/_layout' ,
cache : {
request : {
// Use toISOString() to format your date appropriately
lastModified : myDate . toISOString ( ) // 2015-03-05T08:59:51.491Z
}
}
} urlParams The urlParams property helps protect against invalid cache items (or worse: an attack meant to flood your server's resources by overloading the cache).
return {
next : '/_layout' ,
cache : {
request : {
urlParams : [ 'article' , 'page' ]
}
}
}If we used the example above in our article controller, the following URLs would be cached because the "article" and "page" URL parameters are permitted:
http://cleverna.me/article
http://cleverna.me/article/My-Article-Title
http://cleverna.me/article/My-Article-Title/page/2
The following URLs wouldn't be cached, which is a good thing because it wouldn't take long for an attacker's script to loop over a URL and flood the cache:
http://cleverna.me/article/My-Article-Title/dosattack/1
http://cleverna.me/article/My-Article-Title/dosattack/2
http://cleverna.me/article/My-Article-Title/page/2/dosattack/3
The server logs a warning when invalid URL parameters are present, but continues processing without caching the result.
lifespanThis setting determines how long the request or controller action should remain in the cache, in minutes.
return {
cache : {
request : {
// This cached request will expire in 10 minutes
lifespan : 10
}
}
} resetOnAccess Used with the lifespan setting, resetOnAccess will reset the timer of the route or controller cache whenever it's accessed, keeping it in the cache until traffic subsides. Defaults to true .
return {
cache : {
request : {
// This cached request will expire in 10 minutes, but if a request accesses it
// before then, the cache timer will be reset to 10 minutes from now
lifespan : 10 ,
resetOnAccess : true
}
}
} In most cases, you'll probably want to choose between caching an entire request (URL) or caching individual controller actions, but not both.
When caching an include controller action, the route pathname pointing to that include is used as the cache key. If you use logic to render different views using the same controller action, the first rendered view will be cached. You can pass an additional URL parameter in such cases to get past this limitation and create a unique cache item for different include views.
export const handler = async ( context ) => {
return : {
// Two different versions of the _header include will be cached becaues the URLs are unique
include : context . authenticated ? '/_header/authenticated/true' : '/_header'
}
} citizen's cache is a RAM cache stored in the V8 heap, so be careful with your caching strategy. Use the lifespan and resetOnAccess options so URLs that receive a lot of traffic stay in the cache, while less popular URLs naturally fall out of the cache over time.
By caching static assets in memory, you speed up file serving considerably. To enable static asset caching for your app's public (web) directory, set cache.static.enabled to true in your config:
{
"citizen" : {
"cache" : {
"static" : {
"enabled" : true
}
}
}
}citizen handles response headers automatically (ETags, 304 status codes, etc.) using each file's last modified date. Note that if a file changes after it's been cached, you'll need to clear the file cache using cache.clear() or restart the app.
To clear a file from the cache in a running app:
app . cache . clear ( { file : '/absolute/path/to/file.jpg' } )With static caching enabled, all static files citizen serves will be cached in the V8 heap, so keep an eye on your app's memory usage to make sure you're not using too many resources.
citizen automatically sets ETag headers for cached requests and static assets. You don't need to do anything to make them work. The Cache-Control header is entirely manual, however.
To set the Cache-Control header for static assets, use the cache.control setting in your config:
{
"citizen" : {
"cache" : {
"static" : true ,
"control" : {
"/css/global.css" : " max-age=86400 " ,
"/css/index.css" : " max-age=86400 " ,
"/js/global.js" : " max-age=86400 " ,
"/js/index.js" : " max-age=86400 " ,
"/images/logo.png" : " max-age=31536000 "
}
}
}
} The key name is the pathname that points to the static asset in your web directory. If your app's URL path is /my/app , then this value should be something like /my/app/styles.css . The value is the Cache-Control header value you want to assign to that asset.
You can use strings that match the exact pathname like above, or you can also use wildcards. Mixing the two is fine:
{
"citizen" : {
"cache" : {
"static" : true ,
"control" : {
"/css/*" : " max-age=86400 " ,
"/js/*" : " max-age=86400 " ,
"/images/logo.png" : " max-age=31536000 "
}
}
}
}Here's a great tutorial on client-side caching to help explain ETag and Cache-Control headers.
Both dynamic routes and static assets can be compressed before sending them to the browser. To enable compression for clients that support it:
{
"citizen" : {
"compression" : {
"enabled" : true
}
}
}Proxies, firewalls, and other network circumstances can strip the request header that tells the server to provide compressed assets. You can force gzip or deflate for all clients like this:
{
"citizen" : {
"compression" : {
"enabled" : true ,
"force" : " gzip "
}
}
}If you have request caching enabled, both the original (identity) and compressed (gzip and deflate) versions of the request will be cached, so your cache's memory utilization will increase.
citizen includes basic request payload parsing. When a user submits a form, the parsed form data is available in your controller via params.form . If you want to use a third-party package to parse the form data yourself, you can disable form parsing in the config and access the raw payload via request.payload .
// login controller
export const handler = ( params ) => {
// Set some defaults for the login view
params . form . username = ''
params . form . password = ''
params . form . remember = false
}
// Using a separate action in your controller for form submissions is probably a good idea
export const submit = async ( params ) => {
let authenticate = await app . models . user . authenticate ( {
username : params . form . username ,
password : params . form . password
} ) ,
cookies = { }
if ( authenticate . success ) {
if ( params . form . remember ) {
cookies . username : authenticate . username
}
return {
cookies : cookies ,
redirect : '/'
}
} else {
return {
local : {
message : 'Login failed.'
}
}
}
}If it's a multipart form containing a file, the form object passed to your controller will look something like this:
{
field1 : 'bar' ,
field2 : 'buzz' ,
fileField1 : {
filename : 'file.png' ,
contentType : 'image/png' ,
binary : < binary data >
}
} File contents are presented in binary format, so you'll need to use Buffer.from(fileField1.binary, 'binary') to create the actual file for storage.
You can pass global form settings via citizen.form in the config or at the controller action level via controller config (see below).
Use the maxPayloadSize config to limit form uploads. The following config sets the maxFieldsSize to 512k:
{
"citizen" : {
"forms" : {
"maxPayloadSize" : 500000 // 0.5MB
}
}
} The maxPayloadSize option includes text inputs and files in a multipart form in its calculations. citizen throws an error if form data exceeds this amount.
Certain events will occur throughout the life of your citizen application, or within each request. You can act on these events, execute functions, set directives, and pass the results to the next event or controller via the context argument. For example, you might set a cookie at the beginning of every new session, or check for cookies at the beginning of every request and redirect the user to a login page if they're not authenticated.
To take advantage of these events, include a directory called "hooks" in your app with any or all of following modules and exports:
app/
controllers/
hooks/
application.js // exports start() and error()
request.js // exports start() and end()
response.js // exports start() and end()
session.js // exports start() and end()
request.start() , request.end() , and response.start() are called before your controller is fired, so the output from those events is passed from each one to the next, and ultimately to your controller via the context argument. Exactly what actions they perform and what they output—content, citizen directives, custom directives—is up to you.
All files and exports are optional. citizen parses them at startup and only calls them if they exist. For example, you could have only a request.js module that exports start() .
Here's an example of a request module that checks for a username cookie at the beginning of every request and redirects the user to the login page if it doesn't exist. We also avoid a redirect loop by making sure the requested controller isn't the login controller:
// app/controllers/hooks/request.js
export const start = ( params ) => {
if ( ! params . cookie . username && params . route . controller !== 'login' ) {
return {
redirect = '/login'
}
}
} session.end is slightly different in terms of the arguments it receives, which consists only of a copy of the expired session (no longer active):
// app/controllers/hooks/session.js
export const end = ( expiredSession ) => {
// do something whenever a session ends
} By default, all controllers respond to requests from the host only. citizen supports cross-domain HTTP requests via access control headers.
To enable cross-domain access for individual controller actions, add a cors object with the necessary headers to your controller's exports:
export const config = {
// Each controller action can have its own CORS headers
handler : {
cors : {
'Access-Control-Allow-Origin' : 'http://www.foreignhost.com' ,
'Access-Control-Expose-Headers' : 'X-My-Custom-Header, X-Another-Custom-Header' ,
'Access-Control-Max-Age' : 600 ,
'Access-Control-Allow-Credentials' : 'true' ,
'Access-Control-Allow-Methods' : 'OPTIONS, PUT' ,
'Access-Control-Allow-Headers' : 'Content-Type' ,
'Vary' : 'Origin'
}
}
} Why not just use the HTTP Headers directive or set them manually with response.setHeader() ? When citizen receives a request from an origin other than the host, it checks for the cors export in your controller to provide a preflight response without you having to write your own logic within the controller action. You can of course check request.method and write logic to handle this manually if you prefer.
For more details on CORS, check out the W3C spec and the Mozilla Developer Network.
If you use citizen behind a proxy, such as NGINX or Apache, make sure you have a Forwarded header in your server configuration so citizen handles CORS requests correctly. Different protocols (HTTPS on your load balancer and HTTP in your citizen app) will cause CORS requests to fail without these headers.
Here's an example of how you might set this up in NGINX:
location / {
proxy_set_header Forwarded "for=$remote_addr;host=$host;proto=$scheme;";
proxy_pass http://127.0.0.1:3000;
}
citizen has a built-in application cache where you can store basically anything: strings, objects, buffers, static files, etc.
You can store any object in citizen's cache. The benefits of using cache over storing content in your own global app variables are built-in cache expiration and extension, as well as wrappers for reading, parsing, and storing file content.
citizen's default cache time is 15 minutes, which you can change in the config (see Configuration). Cached item lifespans are extended whenever they're accessed unless you pass resetOnAccess: false or change that setting in the config.
// Cache a string in the default app scope for 15 minutes (default). Keys
// must be unique within a given scope.
app . cache . set ( {
key : 'welcome-message' ,
value : 'Welcome to my site.'
} )
// Cache a string under a custom scope, which is used for retrieving or clearing
// multiple cache items at once. Keys must be unique within a given scope.
// Reserved scope names are "app", "routes", and "files".
app . cache . set ( {
key : 'welcome-message' ,
scope : 'site-messages' ,
value : 'Welcome to our site.'
} )
// Cache a string for the life of the application.
app . cache . set ( {
key : 'welcome-message' ,
value : 'Welcome to my site.' ,
lifespan : 'application'
} )
// Cache a file buffer using the file path as the key. This is a wrapper for
// fs.readFile and fs.readFileSync paired with citizen's cache function.
// Optionally, tell citizen to perform a synchronous file read operation and
// use an encoding different from the default (UTF-8).
app . cache . set ( {
file : '/path/to/articles.txt' ,
synchronous : true ,
encoding : 'CP-1252'
} )
// Cache a file with a custom key. Optionally, parse the JSON and store the
// parsed object in the cache instead of the raw buffer. Expire the cache
// after 10 minutes, regardless of whether the cache is accessed or not.
app . cache . set ( {
file : '/path/to/articles.json' ,
key : 'articles' ,
parseJSON : true ,
lifespan : 10 ,
resetOnAccess : false
} ) app , routes , and files are reserved scope names, so you can't use them for your own custom scopes.
This is a way to check for the existence of a given key or scope in the cache without resetting the cache timer on that item. Returns false if a match isn't found.
// Check for the existence of the specified key
let keyExists = app . cache . exists ( { key : 'welcome-message' } ) // keyExists is true
let keyExists = app . cache . exists ( { file : '/path/to/articles.txt' } ) // keyExists is true
let keyExists = app . cache . exists ( { file : 'articles' } ) // keyExists is true
let keyExists = app . cache . exists ( { key : 'foo' } ) // keyExists is false
// Check the specified scope for the specified key
let keyExists = app . cache . exists ( {
scope : 'site-messages' ,
key : 'welcome-message'
} )
// keyExists is true
// Check if the specified scope exists and contains items
let scopeExists = app . cache . exists ( {
scope : 'site-messages'
} )
// scopeExists is true
// Check if the route cache has any instances of the specified route
let controllerExists = app . cache . exists ( {
route : '/article'
} ) Retrieve an individual key or an entire scope. Returns false if the requested item doesn't exist. If resetOnAccess was true when the item was cached, using retrieve() will reset the cache clock and extend the life of the cached item. If a scope is retrieved, all items in that scope will have their cache timers reset.
Optionally, you can override the resetOnAccess attribute when retrieving a cache item by specifying it inline.
// Retrieve the specified key from the default (app) scope
let welcomeMessage = app . cache . get ( {
key : 'welcome-message'
} )
// Retrieve the specified key from the specified scope and reset its cache timer
// even if resetOnAccess was initially set to false when it was stored
let welcomeMessage = app . cache . get ( {
scope : 'site-messages' ,
key : 'welcome-message' ,
resetOnAccess : true
} )
// Retrieve all keys from the specified scope
let siteMessages = app . cache . get ( {
scope : 'site-messages'
} )
// Retrieve a cached file
let articles = app . cache . get ( {
file : '/path/to/articles.txt'
} )
// Retrieve a cached file with its custom key
let articles = app . cache . get ( {
file : 'articles'
} )Clear a cache object using a key or a scope.
// Store some cache items
app . cache . set ( {
key : 'welcome-message' ,
scope : 'site-messages' ,
value : 'Welcome to our site.'
} )
app . cache . set ( {
key : 'goodbye-message' ,
scope : 'site-messages' ,
value : 'Thanks for visiting!'
} )
app . cache . set ( {
file : '/path/to/articles.txt' ,
synchronous : true
} )
// Clear the welcome message from its custom scope cache
app . cache . clear ( { scope : 'site-messages' , key : 'welcome-message' } )
// Clear all messages from the cache using their custom scope
app . cache . clear ( { scope : 'site-messages' } )
// Clear the articles cache from the file scope
app . cache . clear ( { file : '/path/to/articles.txt' } ) cache.clear() can also be used to delete cached requests and controller actions.
app . cache . clear ( {
route : '/article/My-Article/page/2'
} )
// Clear the entire route scope
app . cache . clear ( { scope : 'routes' } )
// Clear the entire file scope
app . cache . clear ( { scope : 'files' } )
// Clear the entire cache
app . cache . clear ( ) citizen's log() function is exposed for use in your app via app.log() .
Makes it easy to log comments to either the console or a file (or both) in a way that's dependent on the mode of the framework.
When citizen is in production mode, log() does nothing by default. In development mode, log() will log whatever you pass to it. This means you can place it throughout your application's code and it will only write to the log in development mode. You can override this behavior globally with the log settings in your config file or inline with the console or file options when calling log() .
app . log ( {
// Optional. Valid settings are "status" (default) or "error".
type : 'status' ,
// Optional string. Applies a label to your log item.
label : 'Log output' ,
// The content of your log. If it's anything other than a string or
// number, log() will run util.inspect on it and dump the contents.
contents : someObject ,
// Optional. Enables console logs.
console : true ,
// Optional. Enables file logging.
file : false ,
// Optional. File name you'd like to write your log to.
file : 'my-log-file.log' ,
// Optional. Disables the timestamp that normally appears in front of the log
timestamp : false
} ) Log files appear in the directory you specify in config.citizen.directories.logs .
Warning: development mode is inherently insecure. Don't use it in a production environment.
If you set "mode": "development" in your config file, citizen dumps all major operations to the console.
You can also dump the request context to the view by setting development.debug.view in your config file to true , or use the ctzn_debug URL parameter on a per-request basis:
// config file: always dumps debug output in the view
{
"citizen" : {
"development" : {
"debug" : {
"view" : true
}
}
}
} By default, citizen dumps the pattern's complete context. You can specify the exact object to debug with the ctzn_inspect URL parameter:
// Dumps the server params object
http://www.cleverna.me/article/id/237/page/2/ctzn_debug/true/ctzn_inspect/params
// Dumps the user's session scope
http://www.cleverna.me/article/id/237/page/2/ctzn_debug/true/ctzn_inspect/params.session
The debug output traverses objects 4 levels deep by default. To display deeper output, use the development.debug.depth setting in your config file or append ctzn_debugDepth to the URL. Debug rendering will take longer the deeper you go.
// config file: debug 4 levels deep
{
"citizen" : {
"development" : {
"debug" : {
"depth" : 6
}
}
}
}
// URL
// http://www.cleverna.me/article/id/237/page/2/ctzn_debug/true/ctzn_debugDepth/4 In development mode, you must specify the ctzn_debug URL parameter to display debug output. Debug output is disabled in production mode.
The util directory within the citizen package has some helpful utilities.
Creates a complete skeleton of a citizen app with a functional index pattern and error templates.
$ node node_modules/citizen/util/scaffold skeletonResulting file structure:
app/
config/
citizen.json
controllers/
hooks/
application.js
request.js
response.js
session.js
routes/
index.js
models/
index.js
views/
error/
404.html
500.html
ENOENT.html
error.html
index.html
start.js
web/
Run node node_modules/citizen/util/scaffold skeleton -h for options.
Creates a complete citizen MVC pattern. The pattern command takes a pattern name and options:
$ node node_modules/citizen/util/scaffold pattern [options] [pattern] For example, node scaffold pattern article will create the following pattern:
app/
controllers/
routes/
article.js
models/
article.js
views/
article/
article.html
Use node node_modules/citizen/util/scaffold pattern -h to see all available options for customizing your patterns.
(The MIT License)
Copyright (c) 2014-2024 Jay Sylvester
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
By default, citizen dumps the pattern's complete context. You can specify the exact object to debug with the ctzn_inspect URL parameter:
// Dumps the server params object
http://www.cleverna.me/article/id/237/page/2/ctzn_debug/true/ctzn_inspect/params
// Dumps the user's session scope
http://www.cleverna.me/article/id/237/page/2/ctzn_debug/true/ctzn_inspect/params.session
The debug output traverses objects 4 levels deep by default. To display deeper output, use the development.debug.depth setting in your config file or append ctzn_debugDepth to the URL. Debug rendering will take longer the deeper you go.
// config file: debug 4 levels deep
{
"citizen" : {
"development" : {
"debug" : {
"depth" : 6
}
}
}
}
// URL
// http://www.cleverna.me/article/id/237/page/2/ctzn_debug/true/ctzn_debugDepth/4 In development mode, you must specify the ctzn_debug URL parameter to display debug output. Debug output is disabled in production mode.
The util directory within the citizen package has some helpful utilities.
Creates a complete skeleton of a citizen app with a functional index pattern and error templates.
$ node node_modules/citizen/util/scaffold skeletonResulting file structure:
app/
config/
citizen.json
controllers/
hooks/
application.js
request.js
response.js
session.js
routes/
index.js
models/
index.js
views/
error/
404.html
500.html
ENOENT.html
error.html
index.html
start.js
web/
Run node node_modules/citizen/util/scaffold skeleton -h for options.
Creates a complete citizen MVC pattern. The pattern command takes a pattern name and options:
$ node node_modules/citizen/util/scaffold pattern [options] [pattern] For example, node scaffold pattern article will create the following pattern:
app/
controllers/
routes/
article.js
models/
article.js
views/
article/
article.html
Use node node_modules/citizen/util/scaffold pattern -h to see all available options for customizing your patterns.
(The MIT License)
Copyright (c) 2014-2024 Jay Sylvester
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.