I plan to position my backend framework as a website construction framework. This article provides some of my ideas and preliminary practices. If you have a master who has worked on the back-end framework (no language restrictions) in the garden, please advise. The following is a rough process.
The back-end Core File mass. js contains features such as batch creation and deletion of folders, MD5 encryption, type identification, and module loading. At present, the website name and website path are still confused, and will be separately included in a configuration file. You only need to run the node mass. js command to immediately build a template website from the template file. Below is the most importantCode:
// -------- Start to create a website --------- // The Name Of The website you want to create (please correct it here) mass. appname = "jslouvre"; // under which directory the website is created (please correct it here) mass. approot = process. CWD (); // The method used to modify the path. You can pass N parameters to mass. adjustpath = function () {[]. unshift. call (arguments, Mass. approot, Mass. appname); Return require ("path "). join. apply (null, arguments)} var dir = mass. adjustpath ("") // mass. rmdirsync (DIR );//...... mass. require ("HTTP, FS, path, scaffold, intercepters", function (HTTP, FS, path, scaffold, intercepters) {mass. log (" ======================================
", True) if (path. existssync (DIR) {mass. Log (" This website already exists
", True);} else {fs. mkdir (Dir, 0755) Mass. Log (" Start using internal templates to create your website ......
", True);} Global. mapper = scaffold (DIR); // obtain the route system HTTP. createserver (function (req, Res) {var arr = intercepters. concat (); // http://www.cnblogs.com/rubylouvre/archive/2011/05/18/2049989.html for explanation of HTTP status req. on ("err500", function (ERR) {res. writehead (500, {"Content-Type": "text/html"}); var html = FS. readfilesync (mass. adjustpath ("Public/500.html") var arr = [] for (var I in ERR) {arr. push ("
"+ I +": "+ err [I] +"
")
}
Res. Write (HTML + ""). Replace ("{URL}", arr. Join ("")));
Res. End ();
});
Req. On ("next_intercepter", function (){
Try {
VaR next = arr. Shift ();
Next & next. Apply (null, arguments)
} Catch (ERR ){
Req. emit ("err500", err );
}
});
Req. emit ("next_intercepter", req, Res );
}). Listen (8888 );
Console. Log ("start server in 8888 port ")
});
As long as you run mass. JS, it will determine whether the target path exists on this website based on appname and approot, and create the corresponding folder fs. mkdir (Dir, 0755) if not ). But more folders and files are completed by scaffold. js. Folder list in scaffold, used to makeProgramFolder the corresponding files from templatesto the website, and create files such as 505.html, 404.html, favicon. ICO, and routes. js. The most important one is routes, which is used to define routing rules.
// Routes. JS // the most important part is to generate controller, action, model, and viewsmass based on it. define ("Routes", function () {return function (MAP) {// method route // map. get ('/', 'site # Index'); // map. get ('/get_comments/: post_id', 'site # get_comments '); // map. post ('/add_comment', 'site # add_comment '); // resource routing // map. resources ('posts'); // map. resources ('users'); // map. get ('/View/: post_name', 'site # view_post '); // map. get ('/RSS', 'site # RSS '); // map. resources ('posts', {path: 'articles ', as: 'stories'}); // nested route // map. resources ('posts', function (post) {// post. resources ('users'); //}); // namespace route map. namespace ("tests", function (tests) {tests. resources ('comments');}) // map. resources ('users', {// only: ['index', 'show'] //}); // map. resources ('users', {// doesn t: ['create', 'deststroy'] //}); // map. resources ('users', function (User) {// user. get ('Avatar ', 'users # Avatar'); //}); // map. root ("home # Index ")}});
The above is all the contents of routes. js. Five routes can be created: Root route, resource route, method route (get, delete, put, post), namespace route, and nested route. In fact, all of them will be reduced to resource routing, and each URL corresponds to a controller and its action. It will call router. JS, let the router instance mapper in it call the content in Router. JS, and then return mapper.
// Scaffold. JS var routes_url = mass. adjustpath ('config/routes. JS '), action_url = "app/controllers/", view_url = "app/views/", mapper = new router mass. require ("routes (" + routes_url + ")", function (FN) {// read routes. JS configuration file FN (Mapper)}); // save it here and explain return mapper later;
After the router instance mapper runs on routes, n members and elements are added to its attributes. We can use it to further build our controllers, views, and models...
// Such as this. controllers = {}; now {comments: {actions: ['index', 'create', 'new', 'edit', 'deststroy', 'update ', 'show'], views: ['index', 'new', 'edit', 'show'], namespace: 'tests'} // This. get = []; changes to [{Controller: 'comments', Action: 'index', method: 'get', namespace: '/tests/', URL: '/tests/comments.: format? ', Helper: 'tests _ Comments', matcher:/^ \/tests \/comments $/I}, {Controller: 'comments', Action: 'new', method: 'get', namespace: '/tests/', URL: '/tests/comments/New.: format? ', Helper: 'New _ tests_comments', matcher:/^ \/tests \/comments \/new $/I}, {Controller: 'comments', action: 'edit', method: 'get', namespace: '/tests/', URL: '/tests/comments/: ID/edit.: format? ', Helper: 'edit _ tests_comment', matcher:/^ \/tests \/comments \/\ D + \/edit $/I}, {Controller: 'comments ', action: 'show', method: 'get', namespace: '/tests/', URL: '/tests/comments/: ID.: format? ', Helper: 'tests _ comment', matcher:/^ \/tests \/comments \/\ D + $/I}]
Mapper has four array attributes: Get, post, delete, put, which are called matching stacks. The elements of these arrays are all objects, and all objects have a matcher regular attribute, it is used to match the pathname attribute of the requested URL. Of course, we first obtain the method and let the corresponding matching stack process it.
The hand tripod scaffold. JS is also very simple, it will be combined with the hot deployment function in the future, when the user modifies routes. JS or other configuration files, it will automatically generate more views and controllers.
Then we start the server. Because req is an eventemitter instance, we can bind a Custom Event to it at will. Here there are two events next_intercepter and err500. Not to mention err500. next_intercepter is used to start the interceptor cluster. Here we only need to start the first one. It automatically starts the next one in the callback. These interceptors are provided by intercepters. js
Uniform Load.
// Intercepters. jsmass. intercepter = function (FN) {// return function (req, res, err) {If (ERR) {req. emit ("next_intercepter", req, res, err);} else if (FN (req, Res) === true) {req. emit ("next_intercepter", req, Res) }}var deps = ["mime", "postdata", "query", "methodoverride", "JSON ", "favicon", "matcher", "handle404"]; // "more", Mass. define ("intercepters", deps. map (function (STR) {return "intercepters/" + STR }). join (","), function () {console. log ("Get a series of bar interceptors"); return []. slice. call (arguments, 0 )});
Each interceptor processes the raw data and decides to enable the next interceptor. For example, the mime Interceptor:
Mass. define ("intercepters/MIME", function () {console. log ("this module is used to get mime and serve as request. "); Return mass. intercepter (function (req, Res) {console. log ("Enter mime callback"); var STR = req. headers ['content-type'] | ''; req. mime = Str. split (';') [0]; return true ;})})
Postdata interceptor
Mass. define ("intercepters/postdata", "querystring", function (Qs) {console. log ("this module is used to retrieve the data from the POST request and use it as the request. body and "); Return mass. intercepter (function (req, Res) {console. log ("go to postdata callback"); req. body = req. body |{}; if (req. _ body |/get | head /. test (req. method) | 'application/X-WWW-form-urlencoded '! = Req. mime) {return true;} var Buf = ''; req. setencoding ('utf8'); function buildbuffer (chunk) {BUF + = chunk} req. on ('data', buildbuffer); req. once ('end', function () {try {If (BUF! = "") {Req. body = Qs. parse (BUF); req. _ body = true;} req. emit ("next_intercepter", req, Res)} catch (ERR) {req. emit ("next_intercepter", req, res, err)} finally {req. removelistener ("data", buildbuffer )}})});});
Query interceptor
Mass. define ("intercepters/query", "querystring, URL", function (QS, URL) {console. log ("this module is used to obtain URL parameters and convert them into an object as a request. query and "); Return mass. intercepter (function (req, Res) {req. query = ~ Req. url. indexof ('? ')? Qs. parse (URL. parse (req. url). query) :{}; return true ;})})
Methodoverride interceptor
Mass. define ("intercepters/methodoverride", function () {console. log ("this module is used to correct method attributes"); VaR methods = {"put": "put", "delete": "delete"}, method = mass. configs. method | "_ method"; return mass. intercepter (function (req, Res) {req. originalmethod = req. method; var defaultmethod = req. method = "head "? "Get": Req. method; VaR _ method = Req. Body? Req. body [Method]: req. headers ['x-http-method-override'] _ method = (_ method | ""). touppercase (); req. method = methods [_ method] | defaultmethod; If (req. body) {Delete req. body [Method] ;}return true ;})})
JSON interceptor
Mass. define ("intercepters/JSON", function () {console. log ("this module processes the JSON data sent from the front end"); Return mass. intercepter (function (req, res, err) {req. body = req. body |{}; if (req. _ body | 'get' = req. method |! ~ Req. mime. indexof ("JSON") {console. log ("entering JSON callback") return true;} else {var Buf = ''; req. setencoding ('utf8'); function buildbuffer (chunk) {BUF + = chunk;} req. on ('data', buildbuffer); req. once ('end', function () {try {req. body = JSON. parse (BUF); req. _ body = true; req. emit ("next_intercepter", req, Res);} catch (ERR) {err. status = 400; req. emit ("next_intercepter", req, res, err);} finally {req. removelistener ("data", buildbuffer );}});}})})
among so many interceptors, the most important is the matcher interceptor, which enters the entry of the Framework MVC system. Obtain the pathname of the original request, and then use the regular expression to match it. Once a match is reached, the system stops, loads the corresponding controller file, and calls the corresponding action to process the request!
Mass. define ("intercepters/matcher", "url", function (URL) {console. log ("Callback used to match requests") return mass. intercepter (function (req, Res) {console. log ("go to matcher callback"); var pathname = URL. parse (req. URL ). pathname, is404 = true, method = req. method, arr = mapper [Method]; for (VAR I = 0, OBJ; OBJ = arr [I ++];) {If (obj. matcher. test (pathname) {is404 = false var url = mass. adjustpath ("app/controllers/", obj. namespace, obj. Controller + "_ controller. JS ") mass. require (obj. controller + "_ controller (" + URL + ")", function (object) {object [obj. action] (req, Res); // enter the action of the Controller !!! Console. log (obj. action)}, function () {var err = new error; err. statuscode = 404 req. emit ("next_intercepter", req, res, err) ;}) break ;}} if (is404) {var err = new error; err. statuscode = 404 req. emit ("next_intercepter", req, res, err );}})})
handle404 Interceptor:
Mass. define ("intercepters/handle404", "FS, path", function (FS) {console. log ("this module is used to handle 404 errors"); return function (req, res, err) {console. log ("Access handle404 callback"); var accept = req. headers. accept | ''; If (~ Accept. indexof ('html ') {res. writehead (404, {"Content-Type": "text/html"}); var html = FS. readfilesync (mass. adjustpath ("Public/404.html") res. write (HTML + ""). replace ("{URL}", req. URL); Res. end ();} else if (~ Accept. indexof ('json') {// JSON var error = {message: Err. message, Stack: Err. stack}; For (VAR prop in ERR) error [prop] = err [prop]; var JSON = JSON. stringify ({error: Error}); Res. setheader ('content-type', 'application/json'); Res. end (JSON); // plain text} else {res. writehead (res. statuscode, {'content-type': 'text/plain '}); Res. end (err. stack );}}})
Looking back at the Controller part, the Controller generated from the template is very simple:
Mass. define ("comments_controller", function () {return {"Index": function () {}, "CREATE": function () {}, "new": function () {}, "edit": function () {}, "Destroy": function () {}, "Update": function () {}, "show": function () {}}});
So you need to change to its available, such
"Show": function (req, Res) {res. writehead (200, {"Content-Type": "text/html"}); var html = FS. readfilesync (mass. adjustpath ("app/views/tests/show.html") res. write (HTML); Res. end ();}
In the future, the view will be automatically called based on the result of determining the action.
Of course, the framework is still very simple, and it takes only half a day. It must support orm and static file caching. In addition, there are support for cookies and sessions, which can be used as an interceptor.
Summary:
- Determine whether the website exists and fail to build
- Read routes and other configuration files to generate the controllers, views, and models required by the MVC system.
- Through the hot deployment function, you can monitor your modifications to the configuration file to intelligently generate controllers, views, and models.
- Use a series of interceptors for processing. Wait until the matcher interceptor enters the MVC system. Then, use the model to operate the database and render the page. The application of the interceptor cluster greatly improves the scalability of the application. We have not yet had time to develop multi-threading for node. js. Maybe many good things can be found here.
I will upload the relevant code to GitHub later...
This is basically the case. I hope everyone will participate in the discussion.