【打造一個nodejs Web框架(1)】搭建框架

前言

最近有點小空,打算作一個nodejs框架玩玩。做爲一個一直被JAVA和PHP荼毒的developer,因此框架大致會參考PHP的CI,ThinkPHP和JAVA的Jfinal。javascript

預備

其實一個WEB框架並不難,按預約規則搭好能接收request處理request再返回response,就已是一個框架了,而難就是難在如何完善整個框架,去打磨每個環節,例如頁面緩存、數據緩存、與數據庫交互是ORM仍是active record等等等等一大通問題也是決定一個框架質量的因素。css

做爲一個練手項目,我這裏就沒有太遠大志向了。。第一篇的目的就是顯示一下Helloworld,至於框架概念的話也就用咱們熟悉的MVC。html

入口

npm init 項目以後我也加進了babel(被JAVA和PHP荼毒太深,仍是但願寫近JAVAPHP語法的class)前端

圖1. 框架文件架構java

如圖1所示,這是框架的物理文件架構。app裏面裝的就是controller,view和model三個文件夾,裏面放的就是用戶的application文件。core裝的是框架的核心文件。node_modules不用說了吧。test裝的是測試文件。node

index.js 就是框架的入口文件。git

/**
  *  BrownJs
  *
  *  @author SevensChan
  *
 **/
import Brown from './core/Brown.js'

/**
 *   Server Config
**/ 
const HTTP_IP = '127.0.0.1';
const HTTP_PORT = 8899;
var brown = new Brown();
brown.listen(HTTP_PORT,HTTP_IP);
brown.set("view engine","ejs");

主要是調用了core裏面的核心文件Browngithub

import http from 'http'
import router from './Router.js';

let instance = null;

class Brown{

	constructor(){
		if (!instance){
			instance = this;
		}

		this.config = [];

		return instance;
	}
	
	static getInstance(){
		if (!instance){
			instance = new Brown();
		}

		return instance;
	}

	set(key,val){
		this.config[key] = val;
	}

	get(key){
		return this.config[key];
	}

	listen(port,ip){
		try{
			var server = http.createServer(function(req,res){
				router.handle(req,res);
			});
			server.listen(port,ip);
			console.log('Server Start Successfully');
		}catch(err){
			console.log(err);
		}
	}
}

export default Brown;

Brown目前主要做用是用來記錄config參數,以及開啓服務器(後面會有其餘做用,這裏先不提)數據庫

看到createServer中間會把全部請求都轉向到router去處理,router是什麼鬼,下面介紹npm

Router

把請求想象成一列火車,交叉路上就須要有一箇中轉站獲取火車上的信息來決定火車要走向哪條軌道。
這個中轉站就是咱們須要的Router。

Router的主要做用就是讀取request的參數來判斷要調用哪一個controller哪一個方法去處理這個Request

常見的路由規則有
/Controller/Action/Param/querydata

/?controller=x&action=x&param=querydata

在這裏爲了偷懶就用第二個好了

import url from 'url'
import fs from 'fs'
import qs from 'querystring';
import {firstUpperCase} from './Common.js'

//controller緩存
let Controllers = [];

exports.handle = function(req,res){
	req.requrl = url.parse(req.url,true);
	//請求參數
	var queryUrl = qs.parse(url.parse(req.url).query);
	//請求路徑
	var path = req.requrl.pathname;
	//是不是靜態文件
	if (/.(css)$/.test(path)){
		res.writeHead(200,{
			'Content-Type': 'text/css'
		});
		fs.readFile(__dirname+path,'utf8',function(err,data){
			if (err) throw err;
			//輸出靜態文件
			res.write(data,'utf8');
			res.end();
		})
	}else{
		//讀取請求的controller、action
		var controller = queryUrl['cl'];
		var action = queryUrl['ac'];

		var controllerClass;
		var controllerObj;

		//若是沒有指定controller、action 默認爲index
		if (typeof controller === "undefined"){
			controller = 'index';
		}

		if (typeof action === "undefined"){
			action = 'index';
		}

		try{
			//是否有緩存
			if (Controllers[firstUpperCase(controller)]){
				controllerObj = Controllers[firstUpperCase(controller)];
			}else{
				//沒有緩存讀取controller
				controllerClass = require('../app/controller/'+firstUpperCase(controller)+'Controller.js');
				controllerObj = new controllerClass['default']();
				Controllers[firstUpperCase(controller)] = controllerObj;
			}
			//設置request,response和請求參數
			controllerObj.setHttp(req,res,queryUrl);
			//執行action方法
			controllerObj[action]();
		}catch(err){
			console.log(err);
		}
	}
}

而後接下來controller就能夠被call到了

Controller

別人用你的框架固然不能讓別人什麼都要本身寫,因此要準備一堆父類的方法,別人的controller繼承父類的controller就能夠用到一大堆寫好的函數,作一個函二代

import View from './View.js'
class Controller{
	constructor() {
		this.req = null;
		this.res = null;
		this.queryUrl = {};
		//設置view
		this.view = new View();
	}

	//設置request,response和請求參數
	setHttp(req,res,queryUrl){
		this.req = req;
		this.res = res;
		this.queryUrl = queryUrl;
		this.view.setRes(res);
	}

	//獲取get請求的參數
	get(query){
		return this.queryUrl[query];
	}

	//獲取post請求的參數
	post(query){
		//To Be Continue
	}

	//返回當前controller的view對象
	view(){
		return this.view;
	}
}

export default Controller;

在第一篇裏面用到的東西並很少,就主要設置Request,response和獲取參數和View對象,到這一步,來看一下用戶要用controller的時候要怎麼寫

import Controller from '../../core/Controller.js'
class IndexController extends Controller{
	constructor() {
		super();
	}

	index(){
		var cl = super.get('cl');
		super.view().output("Hello World!" + cl);
	}
}

export default IndexController;

其實就這樣了,super.get是父類實現的方法,獲取到參數cl的值 而後經過view的output來輸出。

View

view的話第一篇有兩個目標,第一個直接輸出內容,第二個使用一個模板引擎來實現

import Brown from './Brown.js'
import Engine from './view-engine/engine.js';
/**
 *   View 處理
 *
 **/
class View{
	constructor(){
		this.res = null;
	}

	setRes(res){
		this.res = res;
	}

	output(str){
		const body = str;
		this.res.writeHead(200, {
			'Content-Type': 'text/html' });
		this.res.write(body);
		this.res.end();
	}
}

export default View;

首先完成第一個目標,很簡單,就實現一個output方法調用res去write內容

Hello World

到這裏爲止,咱們能夠測試一下了

圖2. Hello World測試

完美!

使用模板

計劃是本身寫一個模板的,可是以爲這個能夠放在最後再作,因此這裏先實現和 ejs 模板整合

ejs 是js裏面比較有名的前端框架,具體介紹:www.baidu.com/本身搜索

可是咱們整合的話,爲了之後可使用到其餘的模板引擎,是不能直接把ejs代碼嵌入到咱們的View中去的。所以,能夠看到接口的做用了吧,經過定義一個模板引擎接口,提供固定的方法,咱們的View只會關心和調用這些接口的固定方法,至於後面怎麼實現,就另寫引擎接口實現類去作。

翻了翻手冊,發現 js ES6實現接口是屌麻煩的,所以,這些的接口定義先交給規範定義了(意思就是在使用文檔上寫 用這套框架的人要注意了,個人接口要這樣這樣寫,錯了別期望語法會提醒你了)

先寫一個ejs引擎的實現類

import ejs from 'ejs'
import fs from 'fs'
class ejsEngine{
    //render方法顯示模板
	render(res,page,data){
		var body = ejs.renderFile(__dirname +'/../../../app/view/'+page,data,function(err,result){
			if (!err){
				res.writeHead(200, {
				'Content-Type': 'text/html' });
				res.end(result);
			}else{
				res.end(err.toString());
				console.log(err);
			}
		});
	}
}
export default ejsEngine;

renderFile是ejs提供的方法,沒什麼好說的的,讀取到內容就res顯示

而後要和View整合的話,中間最好寫一個工廠類,根據Brown的view engine配置去生成相應的引擎類

//引擎緩存
let Engines = [];

class Engine{
	static createEngine(engineType){
		if (Engines[engineType]){
			return Engines[engineType];
		}
		var engineClass = require('./engine/'+engineType+'Engine.js');
		Engines[engineType] =  new engineClass['default']();
		return Engines[engineType];
	}
}

export default Engine;

對了,就是這樣子了,最後改裝一下View

import Brown from './Brown.js'
import Engine from './view-engine/engine.js';
/**
 *   View 處理
 *
 **/
class View{
	constructor(){
		this.res = null;
		this.engine = Engine.createEngine(Brown.getInstance().get('view engine'));//1
	}

	setRes(res){
		this.res = res;
        this.data = {};
	}

	output(str){
		const body = str;
		this.res.writeHead(200, {
			'Content-Type': 'text/html' });
		this.res.write(body);
		this.res.end();
	}
   
    //2
	display(){
		this.engine.render(this.res,'index.ejs',this.data);
	}
    
    //3
	assign(key,val){
		this.data[key] = val;
	}
}

export default View;

分別多了1,2,3.
1. 注入一個由工廠類生成的引擎對象
2. 顯示模板
3. 注入data

好了,改一下controller

import Controller from '../../core/Controller.js'
class IndexController extends Controller{
	constructor() {
		super();
	}

	index(){
		var cl = super.get('cl');
		super.view().output("Hello World! " + cl);
	}

	show(){
		super.view().assign('title','Hello World');
		super.view().display('index.ejs');
	}

	about(){
		super.view().assign('title','I am ABOUT');
		super.view().display('index.ejs');
	}
}

export default IndexController;

show和about會調用模板去顯示內容,模板很簡單這裏就不放出來了,直接看看最後效果

搞定

 

結論

做爲第一篇是比較簡單的了,裏面也沒有太多的優化,主要把MVC概念清晰起來,而後下一篇就加入數據操做部分,主要參考JFinal

 

更新

項目地址: https://github.com/superhos/brownjs

相關文章
相關標籤/搜索