Citizen هو إطار تطبيق ويب يستند إلى MVC مصمم للأشخاص المهتمين ببناء مواقع ويب سريعة وقابلة للتطوير بسرعة بدلاً من الحفر حول الشجاعة في Node أو تجميع برج Jenga متذبذب مصنوع من 50 حزمة مختلفة.
استخدم Citizen كأساس لتطبيق الويب التقليدي من جانب الخادم ، أو تطبيق وحدات من صفحة واحدة (SPA) ، أو واجهة برمجة تطبيقات RESTful.
كان هناك العديد من التغييرات في الانتقال من 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 في متصفحك وسترى قالب فهرس عاري.
يستخدم المواطن الحرفيين في محرك القالب الافتراضي. يمكنك تثبيت دمج ، وتحديث تكوين القالب ، وتعديل قوالب العرض الافتراضية وفقًا لذلك.
للاطلاع على خيارات التكوين ، راجع التكوين. لمزيد من الأدوات المساعدة لمساعدتك في البدء ، انظر المرافق.
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يمكنك تكوين تطبيق المواطن الخاص بك بملف التكوين وخيارات بدء التشغيل و/أو تكوينات وحدة التحكم المخصصة.
دليل التكوين اختياري ويحتوي على ملفات التكوين بتنسيق JSON الذي يدفع كل من المواطن وتطبيقك. يمكن أن يكون لديك العديد من ملفات تكوين المواطن داخل هذا الدليل ، مما يسمح بتكوينات مختلفة بناءً على البيئة. يعزف المواطن تكوينه بناءً على التسلسل الهرمي التالي:
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 مخصصة للإطار ؛ قم بإنشاء العقدة الخاصة بك (S) لتخزين إعداداتك المخصصة.
يمكنك تعيين تكوين التطبيق الخاص بك عند بدء التشغيل من خلال 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
}
}
} ما يلي يمثل التكوين الافتراضي للمواطن ، والذي يمتد حسب التكوين الخاص بك:
{
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 ، يمكنك توفير نفس الخيارات مثل Node's HTTP.Createserver () و HTTPS.Createserver ().
الفرق الوحيد هو كيفية تمرير ملفات المفاتيح. كما ترون في الأمثلة أعلاه ، يمكنك تمرير مسارات الملف لملفات الملفات الرئيسية. يقرأ المواطن الملفات لك.
| جلسة | يكتب | القيمة الافتراضية | وصف |
|---|---|---|---|
host | خيط | '' | لتحميل ملفات التكوين المختلفة في بيئات مختلفة ، يعتمد Citizen على اسم مضيف الخادم كمفتاح. عند بدء التشغيل ، إذا وجد Citizen ملف تكوين مع مفتاح host يطابق اسم مضيف الخادم ، فإنه يختار ملف التكوين هذا. لا ينبغي الخلط بين هذا مع hostname خادم HTTP (انظر أدناه). |
| مواطن | |||
mode | خيط | يتحقق NODE_ENV أولاً ، وإلا production | يحدد وضع التطبيق بعض سلوكيات وقت التشغيل. القيم المحتملة هي سجلات production development الإنتاج سجلات وحدة التحكم. يمكّن وضع التطوير سجلات وحدة التحكم المطوّلة ، وخيارات تصحيح URL ، واستبدال الوحدة النمطية الساخنة. |
global | خيط | app | تقوم اتفاقية تهيئة المواطن في ملف البدء بتعيين الإطار لمتغير عالمي. الافتراضي ، الذي سترونه المشار إليه خلال الوثائق ، هو app . يمكنك تغيير هذا الإعداد إذا كنت تريد استخدام اسم آخر. |
contentTypes | صفيف | [ 'text/html', 'text/plain', 'application/json', 'application/javascript' ] | قائمة المسمح بتنسيقات الاستجابة لكل طلب ، بناءً على رأس طلب Accept العميل. عند تكوين التنسيقات المتاحة لوحدات التحكم أو الإجراءات الفردية ، يجب توفير مجموعة كاملة من التنسيقات المتاحة. |
errors | خيط | capture | عندما يلقي التطبيق الخاص بك خطأ ، فإن السلوك الافتراضي هو أن يحاول المواطن التعافي من الخطأ والحفاظ على تشغيل التطبيق. يخبر هذا الخيار exit المواطن تسجيل الخطأ والخروج من العملية بدلاً من ذلك. |
templateEngine | خيط | templateLiterals | يستخدم المواطن [القالب الحرفي] (https://developer.mozilla.org/en-us/docs/web/javascript/reference/template_literals) بناء الجملة للعرض الافتراضي. اختياريا ، يمكنك تثبيت دمج واستخدام أي محرك يدعمه (على سبيل المثال ، تثبيت المقاود وضبط templateEngine على handlebars ). |
urlPath | خيط | / | يدل على مسار عنوان URL يؤدي إلى تطبيقك. إذا كنت تريد أن يكون تطبيقك متاحًا عبر http://yoursite.com/my/app وأنك لا تستخدم خادمًا آخر كواجهة أمامية لوكالة الطلب ، فيجب أن يكون هذا الإعداد /my/app (لا تنسى المقطع الرائد). هذا الإعداد مطلوب لجهاز التوجيه للعمل. |
| http | |||
enabled | منطقية | true | يتيح خادم HTTP. |
hostname | خيط | 127.0.0.1 | اسم المضيف الذي يمكن فيه الوصول إلى تطبيقك عبر HTTP. يمكنك تحديد سلسلة فارغة لقبول الطلبات في أي اسم مضيف. |
port | رقم | 3000 | رقم المنفذ الذي يستمع إليه خادم HTTP الخاص بالمواطن للطلبات. |
| https | |||
enabled | منطقية | false | يتيح خادم HTTPS. |
hostname | خيط | 127.0.0.1 | اسم المضيف الذي يمكن فيه الوصول إلى تطبيقك عبر HTTPS. الافتراضي هو مضيف محلي ، ولكن يمكنك تحديد سلسلة فارغة لقبول الطلبات في أي اسم مضيف. |
port | رقم | 443 | رقم المنفذ الذي يستمع إليه خادم HTTPS الخاص بالمواطن للطلبات. |
secureCookies | منطقية | true | افتراضيًا ، تكون جميع ملفات تعريف الارتباط التي يتم تعيينها ضمن طلب HTTPS آمنة. قم بتعيين هذا الخيار إلى false لتجاوز هذا السلوك ، وجعل جميع ملفات تعريف الارتباط غير آمنة ويتطلب منك تعيين الخيار secure يدويًا في توجيه ملفات تعريف الارتباط. |
connectionQueue | عدد صحيح | null | الحد الأقصى لعدد الطلبات الواردة إلى قائمة الانتظار. إذا تركت غير محدد ، يحدد نظام التشغيل حد قائمة الانتظار. |
| جلسات | |||
enabled | منطقية | false | يمكّن نطاق جلسة المستخدم ، الذي يعين كل زائر معرفًا فريدًا ويسمح لك بتخزين البيانات المرتبطة بهذا المعرف داخل خادم التطبيق. |
lifespan | عدد صحيح إيجابي | 20 | إذا تم تمكين الجلسات ، فإن هذا الرقم يمثل طول جلسة المستخدم ، في دقائق. تنتهي الجلسات تلقائيًا إذا كان المستخدم غير نشط لهذا المبلغ من الوقت. |
| تَخطِيط | |||
controller | خيط | '' | إذا كنت تستخدم وحدة تحكم تخطيط عالمية ، فيمكنك تحديد اسم وحدة التحكم هذه هنا بدلاً من استخدام التوجيه next في جميع وحدات التحكم الخاصة بك. |
view | خيط | '' | بشكل افتراضي ، ستستخدم وحدة التحكم في التصميم عرض التخطيط الافتراضي ، ولكن يمكنك تحديد طريقة عرض مختلفة هنا. استخدم اسم الملف بدون ملحق الملف. |
| الأشكال | |||
enabled | منطقية | true | يوفر المواطن معالجة الحمولة الأساسية للنماذج البسيطة. إذا كنت تفضل استخدام حزمة نموذج منفصل ، فقم بتعيين هذا على false . |
maxPayloadSize | عدد صحيح إيجابي | 524288 | الحد الأقصى لحمولة الحمولة النافعة ، في البايتات. اضبط حجم الحمولة الأقصى لمنع الخادم الخاص بك من التحميل الزائد حسب بيانات إدخال النموذج. |
| ضغط | |||
enabled | منطقية | false | يتيح GZIP وانحراف الضغط للآراء المقدمة والأصول الثابتة. |
force | منطقية أو سلسلة | false | قوات GZIP أو تشفير تشفير لجميع العملاء ، حتى لو لم يبلغوا عن قبول التنسيقات المضغوطة. يكسر العديد من الوكلاء وجدران الحماية رأس القبول الذي يحدد دعم GZIP ، وبما أن جميع العملاء المعاصرين يدعمون GZIP ، فعادةً ما يكون من الآمن إجباره على تعيين هذا على gzip ، ولكن يمكنك أيضًا إجبار deflate . |
mimeTypes | صفيف | انظر التكوين الافتراضي أعلاه. | مجموعة من أنواع MIME التي سيتم ضغطها إذا تم تمكين الضغط. راجع نموذج التكوين أعلاه للحصول على القائمة الافتراضية. إذا كنت ترغب في إضافة أو إزالة العناصر ، فيجب استبدال الصفيف بالكامل. |
| مخبأ | |||
control | كائن يحتوي على أزواج مفتاح/قيمة | {} | استخدم هذا الإعداد لتعيين رؤوس التحكم في ذاكرة التخزين المؤقت لوحدات التحكم في المسار والأصول الثابتة. المفتاح هو مسار الأصل ، والقيمة هي رأس التحكم في ذاكرة التخزين المؤقت. انظر التخزين المؤقت من جانب العميل للحصول على التفاصيل. |
invalidUrlParams | خيط | warn | يمكن أن يحدد خيار Route Cache معلمات URL صالحة لمنع تحريك عناوين URL السيئة ، ويحدد invalidUrlParams ما إذا كان سيتم تسجيل تحذير عند مواجهة عناوين URL السيئة أو إلقاء خطأ من جانب العميل. راجع طلبات التخزين المؤقت وإجراءات وحدة التحكم للحصول على التفاصيل. |
| Cache.Application | |||
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 | يتيح ملفات سجل التصحيح. مفيد لمشكلات الإنتاج تصحيح الأخطاء ، ولكن مطوّلة للغاية (نفس السجلات التي ستراها في وحدة التحكم في وضع التطوير). |
maxFileSize | رقم | 10000 | يحدد الحد الأقصى لحجم الملف لملفات السجل ، بالكيلوبايت. عند الوصول إلى الحد ، يتم إعادة تسمية ملف السجل بختم زمني ويتم إنشاء ملف سجل جديد. |
| logs.error | |||
client | منطقية | true | يتيح تسجيل أخطاء العميل 400 مستوى. |
server | منطقية | false | يتيح تسجيل أخطاء خادم/تطبيق 500 مستوى. |
status | منطقية | false | يتحكم في ما إذا كان ينبغي تسجيل رسائل الحالة إلى وحدة التحكم عندما تكون في وضع الإنتاج. (وضع التطوير يسجل دائمًا إلى وحدة التحكم.) |
| logs.Watcher | |||
interval | رقم | 60000 | بالنسبة لأنظمة التشغيل التي لا تدعم أحداث الملفات ، يحدد هذا الموقت عدد المرات التي سيتم فيها استطلاع ملفات السجل للتغييرات قبل الأرشفة ، بالميلي ثانية. |
| تطوير | |||
| Development.debug | |||
scope | هدف | يحدد هذا الإعداد النطاقات التي يتم تسجيلها في إخراج التصحيح في وضع التطوير. بشكل افتراضي ، يتم تمكين جميع النطاقات. | |
depth | عدد صحيح إيجابي | 3 | عندما يقوم المواطن بتفريغ كائن في محتوى التصحيح ، فإنه يتفقده باستخدام node's util.inspect. يحدد هذا الإعداد عمق الفحص ، مما يعني عدد العقد التي سيتم فحصها وعرضها. أعداد أكبر تعني فحص أعمق وأداء أبطأ. |
view | منطقية | false | اضبط هذا على True لتفريغ معلومات التصحيح مباشرة في عرض HTML. |
enableCache | منطقية | false | وضع التطوير يعطل ذاكرة التخزين المؤقت. قم بتغيير هذا الإعداد إلى true لتمكين ذاكرة التخزين المؤقت في وضع التطوير. |
| Development.Watcher | |||
custom | صفيف | يمكنك إخبار استبدال الوحدة النمطية الساخنة للمواطن لمشاهدة الوحدات المخصصة الخاصة بك. يمكن أن تحتوي هذه الصفيف على كائنات ذات 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 ، ويوفر مساحة كبيرة للمحتوى الصديق لكبار المسئولين الاقتصاديين الذي يمكن أن يتضاعف كمعرف فريد. الهيكل يشبه هذا:
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
يتيح لك المواطن أيضًا إدراج المحتوى ذي الصلة اختياريًا في عناوين URL الخاصة بك ، مثل ذلك:
http://www.cleverna.me/article/My-Clever-Article-Title/page/2
يجب أن يتبع محتوى SEO هذا دائمًا اسم وحدة التحكم ويسبق أي أزواج اسم/قيمة ، بما في ذلك إجراء وحدة التحكم. يمكنك الوصول إليها بشكل عام عبر route.descriptor أو داخل نطاق url ( url.article في هذه الحالة) ، مما يعني أنه يمكنك استخدامه كمعرف فريد (المزيد على معلمات URL في قسم وحدات التحكم في الطريق).
يتم حجز action معلمات URL و direct للإطار ، لذلك لا تستخدمها لتطبيقك.
يعتمد المواطن على اتفاقية بسيطة للسيطرة على عرض النموذج. قد يستخدم نمط المقالة المذكور أعلاه الهيكل التالي:
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
المزيد عن المشاهدات في قسم المشاهدات.
النماذج ووجهات النظر اختيارية ولا تحتاج بالضرورة إلى ربطها بوحدة تحكم معينة. إذا كانت وحدة تحكم المسار الخاصة بك ستنقل إخراجها إلى وحدة تحكم أخرى لمزيد من المعالجة وتقديم النهائي ، فلن تحتاج إلى تضمين عرض مطابقة (انظر وحدة التحكم التالية).
وحدة تحكم طريق المواطن هي مجرد وحدة 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
}
} يقوم خادم المواطن باستدعاء 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
}
}
} يمكن طلب إجراءات بديلة باستخدام معلمة عنوان URL action . على سبيل المثال ، ربما نريد إجراءً مختلفًا وعرضًا لتحرير مقال:
// 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'
}
} يمكنك وضع أي بيانات ترغب في نقلها إلى المواطن ضمن بيان return . يجب نقل جميع البيانات التي تريد تقديمها في وجهة نظرك إلى المواطن داخل كائن يسمى local ، كما هو موضح أعلاه. يمكن تمرير كائنات إضافية إلى المواطن لوضع توجيهات توفر إرشادات للخادم (انظر توجيهات وحدة التحكم). يمكنك حتى إضافة الكائنات الخاصة بك إلى السياق وتمريرها من وحدة التحكم إلى وحدة التحكم (المزيد في قسم Controller Conning.)
النماذج هي وحدات اختيارية وهيكلها متروك لك تمامًا. المواطن لا يتحدث إلى نماذجك مباشرة ؛ إنه يخزنهم فقط في 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
} يستخدم Citizen قالب حرفي لعرض عرض افتراضيًا. يمكنك تثبيت consodiday.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 عن طريق تعيين رأس Accept المناسب في طلبك ، مما يتيح للمورد نفسه تقديم عرض HTML الكامل و JSON لطلبات AJAX وواجهة برمجة التطبيقات المريحة.
سيعود إجراء وحدة تحكم Route Route 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 )
} يخزن المواطن جميع الوحدات النمطية في نطاق 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 ورمي الطريق خطأ ، فسيقوم Citizen بإرجاع الخطأ بتنسيق 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
بالإضافة إلى عرض البيانات ، يمكن أن تمرر عبارة إرجاع إجراء وحدة التحكم في المسار أيضًا بتوجيهات لتقديم طرق عرض بديلة ، وتعيين ملفات تعريف الارتباط ومتغيرات الجلسة ، وبدء عمليات إعادة التوجيه ، وتشمل الاتصالات والعرض ، وتصرفات وحدة التحكم في مسار ذاكرة التخزين المؤقت (أو الطلبات بأكملها) ، وتسليم الطلب إلى وحدة تحكم أخرى لمزيد من المعالجة.
بشكل افتراضي ، يقوم الخادم بإعداد طريقة العرض التي يطابق اسمها اسم وحدة التحكم. لتقديم طريقة عرض مختلفة ، استخدم توجيه 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
} بمجرد تعيين ملفات تعريف الارتباط على العميل ، فهي متوفرة في params.cookie داخل وحدات التحكم وببساطة 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 Config ، والتي تمثل طول الجلسة في دقائق. الافتراضي 20 دقيقة. يتم إعادة تعيين timer مع كل طلب من المستخدم. عندما ينفد timer ، يتم حذف الجلسة. أي طلبات عميل بعد ذلك الوقت ستقوم بإنشاء معرف جلسة جديد وإرسال ملف تعريف ارتباط معرف جلسة جديد إلى العميل.
لتوضيح وإنهاء صلاحية جلسة المستخدم الحالية:
return {
session : {
expires : 'now'
}
} تبدأ جميع متغيرات الجلسة التي حددها المواطن ببادئة ctzn_ لتجنب الاصطدامات. لا تبدأ أسماء متغيرات الجلسة مع ctzn_ ولا ينبغي أن تواجه أي مشاكل.
يمكنك تمرير تعليمات إعادة توجيه إلى الخادم والتي سيتم بدء تشغيلها بعد معالجة إجراء وحدة التحكم.
يأخذ الكائن redirect سلسلة عنوان URL في نسخته المختصرة ، أو ثلاثة خيارات: statusCode و url و refresh . إذا لم تقدم رمز الحالة ، فإن Citizen يستخدم 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 ، فسيقوم Citizen بإرسال طريقة عرض تم تقديمها إلى العميل لأن إعادة التوجيه تحدث من جانب العميل.
باستخدام رأس رأس الموقع (في رأيي) رأس المرجع لأن المرجع لا ينتهي به الأمر إلى أن يكون المورد الذي بدأ إعادة التوجيه ، ولكن المورد قبل الصفحة التي بدأها. للتغلب على هذه المشكلة ، يقوم Citizen بتخزين متغير جلسة يسمى ctzn_referer الذي يحتوي على عنوان URL للمورد الذي بدأ إعادة التوجيه ، والذي يمكنك استخدامه لإعادة توجيه المستخدمين بشكل صحيح. على سبيل المثال ، إذا حاول أحد المستخدمين غير المصدق الوصول إلى صفحة آمنة وإعادة توجيهها إلى نموذج تسجيل الدخول ، فسيتم تخزين عنوان الصفحة الآمنة في ctzn_referer حتى تتمكن من إرسالها إلى هناك بدلاً من الصفحة السابقة.
إذا لم تقم بتمكين الجلسات ، فإن Citizen يعود إلى إنشاء ملف تعريف ارتباط يسمى ctzn_referer بدلاً من ذلك.
إذا كنت تستخدم مواطنًا خلف وكيل ، مثل Nginx أو Apache ، تأكد من أن لديك رأس HTTP Forwarded في تكوين الخادم الخاص بك بحيث يعمل 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 استخدام أنماط MVC كاملة كما يتضمن ، وهي نسخة من المكونات للمواطنين. لكل منهما وحدة تحكم وطراز وموديل (عرض) خاص بها. يمكن استخدامه لإجراء إجراء أو إرجاع عرض كامل تم تقديمه. يمكن أن يكون أي وحدة تحكم مسار تشمل.
دعنا نقول أن قالب نمط مقالنا له المحتويات التالية. يحتوي قسم الرأس على بيانات ميتا ديناميكية ، ويتغير محتوى الرأس اعتمادًا على ما إذا كان المستخدم قد تم تسجيل الدخول أم لا:
<!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 أعلاه Citizen استدعاء وحدات التحكم _head و _header ، وتمريرها نفس الوسائط التي تم تمريرها إلى وحدة تحكم article (params ، الطلب ، الاستجابة ، السياق) ، تقديم وجهات نظرها ، وإضافة طرق العرض الناتجة إلى سياق العرض.
إليك ما قد يبدو عليه وحدة تحكم قسم رأسنا:
// _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 ، أو مدخلات النماذج ، أو سياق الطلب ، وما إلى ذلك) مثل وحدة تحكم الاتصال ، فإن البيانات التي تم إنشاؤها داخل تضمين لم يتم نقلها إلى المتصل.
يمكن الوصول إلى نمط من المفترض أن يتم استخدامه كتضمين عبر HTTP تمامًا مثل أي وحدة تحكم مسار أخرى. يمكنك طلب وحدة التحكم _header مثل ذلك وتلقي جزءًا من HTML أو JSON كرد:
http://cleverna.me/_header
يعد هذا أمرًا رائعًا للتعامل مع خادم الطلب الأول ثم تحديث المحتوى باستخدام مكتبة من جانب العميل.
يتضمن المواطن توفير وظائف غنية ، لكن لديهم قيود ويمكن أن يكون مبالغة في بعض المواقف.
يتيح لك Citizen سلسلة من وحدات التحكم في المسار المتعددة معًا في سلسلة من طلب واحد باستخدام التوجيه next . تقوم وحدة التحكم المطلوبة بتمرير بياناتها وعرضها إلى وحدة تحكم لاحقة ، وإضافة بياناتها الخاصة وتقديم عرضها الخاص.
يمكنك ربط أكبر عدد من وحدات التحكم في الطريق في طلب واحد كما تريد. سيكون لكل وحدة تحكم مسار بياناتها وعرض الإخراج المخزن في كائن params.route.chain .
// 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
}
}كل وحدة تحكم في السلسلة لديها إمكانية الوصول إلى سياق وحدات التحكم السابقة ووجهات النظر. يوفر وحدة التحكم الأخيرة في السلسلة العرض النهائي المقدم. 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.