• Posts tagged "express3"

Blog Archives

Node.js开发框架Express4.x

从零开始nodejs系列文章,将介绍如何利Javascript做为服务端脚本,通过Nodejs框架web开发。Nodejs框架是基于V8的引擎,是目前速度最快的Javascript引擎。chrome浏览器就基于V8,同时打开20-30个网页都很流畅。Nodejs标准的web开发框架Express,可以帮助我们迅速建立web站点,比起PHP的开发效率更高,而且学习曲线更低。非常适合小型网站,个性化网站,我们自己的Geek网站!!

关于作者

  • 张丹(Conan), 创业者,程序员(Java,R,Javascript)
  • weibo:@Conan_Z
  • blog: http://blog.fens.me
  • email: bsspirit@gmail.com

转载请注明出处:
http://blog.fens.me/nodejs-express4/

express4

前言

Nodejs是一个年轻的编程框架,充满了活力和无限激情,一直都在保持着快速更新。基于Nodejs的官方Web开发库Express也在同步发展着,每年升级一个大版本,甚至对框架底层都做了大手术。在Express4时,替换掉中件间库connect,而改用多个更细粒度的库来取代。带来的好处是明显地,这些中间件能更自由的更新和发布,不会受到Express发布周期的影响;但问题也是很的棘手,不兼容于之前的版本,升级就意味着要修改代码。

之前写过一篇文章“Nodejs开发框架Express3.0开发手记–从零开始”,很多新学Node的朋友都在参考,但由于Express已经升级,文章中有部分的代码已经不能使用,所以就有了这篇介绍Express4.x的文章。

目录

  1. 建立工程
  2. 目录结构
  3. package.json项目配置
  4. app.js核心文件
  5. Bootstrap界面框架
  6. 路由功能
  7. 程序代码
  8. Express3.x和Express4.x的改动列表

1. 建立项目

让我们从头开始Express4.x的安装和使用吧,安装Node和NPM在本文就不多说了。Linux环境安装请参考文章,准备Nodejs开发环境Ubuntu,Window环境安装直接下载Node的安装文件,双击安装就行了。

我的系统环境

  • Win7 64bit
  • Nodejs:v0.10.31
  • Npm:1.4.23

首先,我们需要安装express库。在Express3.6.x之前的版本,Express需要全局安装的,项目构建器模块是合并在Express项目中的,后来这个构建器被拆分出来,独立成为了一个项目express-generator,现在我们只需要全局安装express-generator项目就行了。


~ npm install -g express-generator@4  #全局安装-g
C:\Users\Administrator\AppData\Roaming\npm\express -> C:\Users\Administrator\AppData\Roaming\npm\node_modules\express-ge
nerator\bin\express

express-generator@4.11.2 C:\Users\Administrator\AppData\Roaming\npm\node_modules\express-generator
├── sorted-object@1.0.0
├── commander@2.6.0
└── mkdirp@0.5.0 (minimist@0.0.8)

安装好express-generator包后,我们在命令行就可以使用express命令了。


~ express -V # 检查express的版本
4.11.2

~ express -h  # 检查看express的帮助命令
  Usage: express [options] [dir]
  Options:
    -h, --help          output usage information
    -V, --version       output the version number
    -e, --ejs           add ejs engine support (defaults to jade)
        --hbs           add handlebars engine support
    -H, --hogan         add hogan.js engine support
    -c, --css   add stylesheet  support (less|stylus|compass) (defaults to plain css)
        --git           add .gitignore
    -f, --force         force on non-empty directory

接下来,我们使用express的命令,来创建项目了。


~ cd D:\workspace\javascript  # 进入工作目录

~ D:\workspace\javascript>express -e nodejs-demo  # 创建项目
   create : nodejs-demo
   create : nodejs-demo/package.json
   create : nodejs-demo/app.js
   create : nodejs-demo/public/javascripts
   create : nodejs-demo/public/images
   create : nodejs-demo/public
   create : nodejs-demo/public/stylesheets
   create : nodejs-demo/public/stylesheets/style.css
   create : nodejs-demo/views
   create : nodejs-demo/views/index.ejs
   create : nodejs-demo/views/error.ejs
   create : nodejs-demo/routes
   create : nodejs-demo/routes/index.js
   create : nodejs-demo/routes/users.js
   create : nodejs-demo/bin
   create : nodejs-demo/bin/www

   install dependencies:
     $ cd nodejs-demo && npm install
   run the app:
     $ DEBUG=nodejs-demo:* ./bin/www

进入项目目录,下载依赖库,构建项目。


~ D:\workspace\javascript>cd nodejs-demo && npm install

启动项目。


~ D:\workspace\javascript\nodejs-demo>npm start

> express4-demo@0.0.0 start D:\workspace\javascript\nodejs-demo
> node ./bin/www

module.js:338
    throw err;
          ^
Error: Cannot find module './routes/users'
    at Function.Module._resolveFilename (module.js:336:15)
    at Function.Module._load (module.js:278:25)
    at Module.require (module.js:365:17)
    at require (module.js:384:17)
    at Object. (D:\workspace\javascript\nodejs-demo\app.js:9:13)
    at Module._compile (module.js:460:26)
    at Object.Module._extensions..js (module.js:478:10)
    at Module.load (module.js:355:32)
    at Function.Module._load (module.js:310:12)
    at Module.require (module.js:365:17)

第一次启动发生了错误,可能是express-generator和express不匹配造成的,找到问题在app.js文件中,注释第9行和第26行。


..
//var users = require('./routes/users');
..
//app.use('/users', users);       
..

再次启动项目。


D:\workspace\javascript\nodejs-demo>npm start
> express4-demo@0.0.0 start D:\workspace\javascript\nodejs-demo
> node ./bin/www

项目启动成功,打开浏览器 http://localhost:3000,就可以看到显示的页面了。
express_1

这样非常简单地,我们就把一个最基本的Web应用做好了,就是几条命令而已。

2. 目录结构

接下来,我们详细看一下Express4项目的结构、配置和使用。

  • bin, 存放启动项目的脚本文件
  • node_modules, 存放所有的项目依赖库。
  • public,静态文件(css,js,img)
  • routes,路由文件(MVC中的C,controller)
  • views,页面文件(Ejs模板)
  • package.json,项目依赖配置及开发者信息
  • app.js,应用核心配置文件

express_2

3. package.json项目配置

package.json用于项目依赖配置及开发者信息,scripts属性是用于定义操作命令的,可以非常方便的增加启动命令,比如默认的start,用npm start代表执行node ./bin/www命令。

查看package.json文件。


{
  "name": "express4-demo",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "node ./bin/www"
  },
  "dependencies": {
    "body-parser": "~1.10.2",
    "cookie-parser": "~1.3.3",
    "debug": "~2.1.1",
    "ejs": "~2.2.3",
    "express": "~4.11.1",
    "morgan": "~1.5.1",
    "serve-favicon": "~2.2.0"
  }
}

4. app.js核心文件

从Express3.x升级到Express4.x,主要的变化就在app.js文件中。查看app.js文件,我已经增加注释说明。


// 加载依赖库,原来这个类库都封装在connect中,现在需地注单独加载
var express = require('express'); 
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');

// 加载路由控制
var routes = require('./routes/index');
//var users = require('./routes/users');

// 创建项目实例
var app = express();

// 定义EJS模板引擎和模板文件位置,也可以使用jade或其他模型引擎
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

// 定义icon图标
app.use(favicon(__dirname + '/public/favicon.ico'));
// 定义日志和输出级别
app.use(logger('dev'));
// 定义数据解析器
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
// 定义cookie解析器
app.use(cookieParser());
// 定义静态文件目录
app.use(express.static(path.join(__dirname, 'public')));

// 匹配路径和路由
app.use('/', routes);
//app.use('/users', users);

// 404错误处理
app.use(function(req, res, next) {
    var err = new Error('Not Found');
    err.status = 404;
    next(err);
});

// 开发环境,500错误处理和错误堆栈跟踪
if (app.get('env') === 'development') {
    app.use(function(err, req, res, next) {
        res.status(err.status || 500);
        res.render('error', {
            message: err.message,
            error: err
        });
    });
}

// 生产环境,500错误处理
app.use(function(err, req, res, next) {
    res.status(err.status || 500);
    res.render('error', {
        message: err.message,
        error: {}
    });
});

// 输出模型app
module.exports = app;

我们看到在app.js中,原来调用connect库的部分都被其他的库所代替,serve-favicon、morgan、cookie-parser、body-parser,默认项目中,只用到了最基本的几个库,还没有其他需要替换的库,在本文最后有详细列出。

另外,原来用于项目启动代码也被移到./bin/www的文件,www文件也是一个node的脚本,用于分离配置和启动程序。

查看./bin/www文件。


#!/usr/bin/env node   

/**
 * 依赖加载
 */
var app = require('../app');
var debug = require('debug')('nodejs-demo:server');
var http = require('http');

/**
 * 定义启动端口
 */
var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);

/**
 * 创建HTTP服务器实例
 */
var server = http.createServer(app);

/**
 * 启动网络服务监听端口
 */
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);

/**
 * 端口标准化函数
 */
function normalizePort(val) {
  var port = parseInt(val, 10);
  if (isNaN(port)) {
    return val;
  }
  if (port >= 0) {
    return port;
  }
  return false;
}

/**
 * HTTP异常事件处理函数
 */
function onError(error) {
  if (error.syscall !== 'listen') {
    throw error;
  }

  var bind = typeof port === 'string'
    ? 'Pipe ' + port
    : 'Port ' + port

  // handle specific listen errors with friendly messages
  switch (error.code) {
    case 'EACCES':
      console.error(bind + ' requires elevated privileges');
      process.exit(1);
      break;
    case 'EADDRINUSE':
      console.error(bind + ' is already in use');
      process.exit(1);
      break;
    default:
      throw error;
  }
}

/**
 * 事件绑定函数
 */
function onListening() {
  var addr = server.address();
  var bind = typeof addr === 'string'
    ? 'pipe ' + addr
    : 'port ' + addr.port;
  debug('Listening on ' + bind);
}

5. Bootstrap界面框架

创建Bootstrap界面框架,直接在index.ejs文件上面做修改。可以手动下载Bootstrap库放到项目中对应的位置引用,也可以通过bower来管理前端的Javascript库,参考文章 bower解决js的依赖管理。另外还可以直接使用免费的CDN源加载Bootstrap的css和js文件。下面我就直接使用Bootcss社区提供的CDN源加载Bootstrap。

编辑views/index.ejs文件


<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <title><%= title %></title>
    <link rel="stylesheet" href="http://cdn.bootcss.com/bootstrap/3.3.2/css/bootstrap.min.css">
    <link rel='stylesheet' href='/stylesheets/style.css' />
  </head>
  <body>
    <div class="well jumbotron">
      <h1><%= title %></h1>
      <p>This is a simple hero unit, a simple jumbotron-style component for calling extra attention to featured content or information.</p>
      <p><a class="btn btn-primary btn-lg" href="#" role="button">Learn more</a></p>
    </div>
    <script src="http://cdn.bootcss.com/jquery/1.11.2/jquery.min.js"></script>
    <script src="http://cdn.bootcss.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>
  </body>
</html>

效果如下,已经加入了bootstrap的样式了。

express_3

接下来,我们把index.ejs页面切分成3个部分:header.ejs, index.ejs, footer.ejs,用于网站页面的模块化。

  • header.ejs, 为页面的头部区域
  • index.ejs, 为内容显示区域
  • footer.ejs,为页面底部区域

编辑header.ejs。


<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <title><%= title %></title>
    <link rel="stylesheet" href="http://cdn.bootcss.com/bootstrap/3.3.2/css/bootstrap.min.css">
    <link rel='stylesheet' href='/stylesheets/style.css' />
  </head>
  <body>

编辑footer.ejs。


    <script src="http://cdn.bootcss.com/jquery/1.11.2/jquery.min.js"></script>
    <script src="http://cdn.bootcss.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>
  </body>
</html>

编辑index.ejs。


<% include header.ejs %>

<div class="well jumbotron">
<h1><%= title %></h1>
<p>This is a simple hero unit, a simple jumbotron-style component for calling extra attention to featured content or information.</p>
<p><a class="btn btn-primary btn-lg" href="#" role="button">Learn more</a></p>
</div>

<% include footer.ejs %>

把页表和页底的代码分离后,让index.ejs页面的核心代码更少,更容易维护。

6. 路由功能

路由功能,是Express4以后全面改版的功能。在应用程序加载隐含路由中间件,不用担心在中间件被装载相对于路由器中间件的顺序。定义路由的方式是不变的,路由系统中增加2个新的功能。

  • app.route()函数,创建可链接的途径处理程序的路由路径。
  • express.Router类,创建模块化安装路径的处理程序。

app.route方法会返回一个Route实例,它可以继续使用所有的HTTP方法,包括get,post,all,put,delete,head等。


app.route('/users')
  .get(function(req, res, next) {})
  .post(function(req, res, next) {})

express.Router类,则可以帮助我们更好的组织代码结构。在app.js文件中,定义了app.use(‘/’, routes); routes是指向了routes目录下的index.js文件,./routes/index.js文件中,express.Router被定义使用,路径/*处理都会由routes/index.js文件里的Router来处理。如果我们要管理不同的路径,那么可以直接配置为多个不同的Router。


app.use('/user', require('./routes/user').user);
app.use('/admin', require('./routes/admin').admin);
app.use('/', require('./routes'));

7. 程序代码

对于刚接触Express4.x的朋友,可以直接从Github上面下载本文项目中的源代码,按照片文章中的介绍学习Express4,下载地址:https://github.com/bsspirit/nodejs-demo/tree/express4

也可以直接用github命令行来下载:


~ git clone git@github.com:bsspirit/nodejs-demo.git   # 下载github项目
~ cd nodejs-demo                                      # 进入下载目录
~ git checkout express4                               # 切换到express4的分支
~ npm install                                         # 下载依赖库
~ npm start                                           # 启动服务器

注:Github上本项目有3分支,express3和master分支都是express3的例子,express4分支是本文的例子。

当然,本文对express4的介绍其实还远远不够,除了文中说到的express改动的部分,其他的部分都express3类似,所以更详细的使用说明,还请大家同时参考文章,Nodejs开发框架Express3.0开发手记–从零开始 来一起学习。

8. Express3.x和Express4.x的改动列表

最后,列举一下3.x的内置模块和4.x模块的对照表,我们可以发现大部分都是connect部分的替换。关于connect库的详细介绍,可以参考文章,Nodejs基础中间件Connect

Express 3Express 4
express.bodyParserbody-parser +
multer
express.compresscompression
express.cookieSessioncookie-session
express.cookieParsercookie-parser
express.loggermorgan
express.sessionexpress-session
express.faviconserve-favicon
express.responseTimeresponse-time
express.errorHandlererrorhandler
express.methodOverridemethod-override
express.timeoutconnect-timeout
express.vhostvhost
express.csrfcsurf
express.directoryserve-index
express.staticserve-static

希望能给入门Nodejs的朋友一些帮助,同时我公司也在招Nodejs/Javascript全端工程师,欢迎有实力、愿意创业的朋友与我联系,bsspirit@gmail.com 或 微博@Conan_Z。

转载请注明出处:
http://blog.fens.me/nodejs-express4/

打赏作者

快速搭建Web环境 Angularjs + Express3 + Bootstrap3

AngularJS体验式编程系列文章,将介绍如何用angularjs构建一个强大的web前端系统。angularjs是由Google团队开发的一款非常优秀web前端框架。在当前如此多的web框架下,angularjs能脱颖而出,从架构设计上就高人一等,双向数据绑定,依赖注入,指令,MVC,模板。Angular.js创新地把后台技术融入前端开发,扫去jQuery一度的光芒。用angularjs就像写后台代码,更规范,更结构化,更可控。

关于作者

  • 张丹(Conan), 程序员Java,R,PHP,Javascript
  • weibo:@Conan_Z
  • blog: http://blog.fens.me
  • email: bsspirit@gmail.com

转载请注明出处:
http://blog.fens.me/angularjs-express3-bootstrap3/

angular-basic

前言

Angularjs越用越顺手,不仅代码量比jQuery少很多,而且实现思路特别清晰,构建大型的Web前端项目,真是最适合不过了。

Bootstrap让界面美观大方,就连像我这种不懂UE的人,也能做出专业级的水准。再结合Nodejs的Express做后端,三剑合并,太无敌了,大有统一前端开发的趋势,前途不可估量!

目录

  1. 从零开始手工创建Express3项目
  2. 新建Angularjs目录及文件
  3. 配置bower
  4. 配置Angularjs项目
  5. 增加Bootstrap
  6. 完整的项目

1. 从零开始手工创建Express3项目

系统环境:

  • Win7 64bit 旗舰版
  • node v0.10.5
  • npm 1.2.19
  • bower 1.1.2

本文截图中使用的开发工具是WebStorm,请参考文章:AngularJS最理想开发工具WebStorm

创建express项目


~ D:\workspace\javascript>express -e angular-basic
~ D:\workspace\javascript>cd angular-basic && npm install

生成的express项目目录

express

修改app.js的配置

  • 修改ejs: 文件扩展名ejs为html
  • 设置angular: 启动路径为”/”
  • 设置angular: 启动文件为app/index.html

~ vi app.js

var express = require('express')
    , path = require('path')
    , ejs = require('ejs')
    , app = express()
    , server = require('http').createServer(app);

app.set('port', process.env.PORT || 3000);
app.set('views', __dirname + '/views');
app.engine('.html', ejs.__express);
app.set('view engine', 'html'); //替换文件扩展名ejs为html
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(path.join(__dirname, 'app')));

if (app.get('env') === 'development') {
    app.use(express.errorHandler());
}

// angular启动页
app.get('/', function (req, res) {
    res.sendfile('app/index.html');
});

server.listen(app.get('port'), function () {
    console.log('Express server listening on port ' + app.get('port'));
});

如何单独创建Express3的项目,请参考文章:Nodejs开发框架Express3.0开发手记–从零开始

2. 新建Angularjs目录及文件

创建Angularjs需要的目录及文件


D:\workspace\javascript\angular-basic>mkdir app
D:\workspace\javascript\angular-basic>mkdir app\scripts
D:\workspace\javascript\angular-basic>mkdir app\scripts\angular
D:\workspace\javascript\angular-basic>mkdir app\styles
D:\workspace\javascript\angular-basic>mkdir app\views
D:\workspace\javascript\angular-basic>mkdir app\views\component
D:\workspace\javascript\angular-basic>mkdir app\views\tpl

D:\workspace\javascript\angular-basic>touch app\index.html
D:\workspace\javascript\angular-basic>touch app\scripts\angular\app.js
D:\workspace\javascript\angular-basic>touch app\scripts\angular\controllers.js
D:\workspace\javascript\angular-basic>touch app\styles\main.css
D:\workspace\javascript\angular-basic>touch app\views\tpl\welcome.html

D:\workspace\javascript\angular-basic>echo "aaaa" > app\index.html

创建的Angularjs目录及文件

angular

目录解释:

  • app目录: Angular项目的根目录
  • scripts目录: 存放Javascript脚本目录
  • scripts\angular目录: 存放Angular Javascript脚本目录
  • styles目录: 存放css的目录
  • views目录: 存放html的目录
  • views\component目录: 存放html的自定义组件目录
  • views\tpl目录: 存放html的目录

文件解释:

  • app\index.html: Angular项目的入口文件
  • styles\main.css: Angular项目的css文件
  • scripts\angular\app.js: Angular项目全局配置文件
  • scripts\angular\controllers.js: Angular项目全局控制器文件/li>
  • views\tpl\welcome.html: 欢迎页

删除不需要的文件目录


D:\workspace\javascript\angular-basic>rm -rf public
D:\workspace\javascript\angular-basic>rm -rf routes

启动node服务器,检查入口页的配置


D:\workspace\javascript\angular-basic>node app.js
Express server listening on port 3000
GET / 200 11ms - 9b

indexpage

界面显示”aaaa”,说明node启动express,已经指向到app\index.html的页面。

3. 配置bower

接下来,我要通过bower来安装Angularjs和Bootstrap,以及其他依赖的前端库。关于bower的详细使用,请参考文章:bower解决js的依赖管理

新建文件:

    • .bowerrc: bower的环境设置,用于指定bower的依赖库的存放位置
    • bower.json: bower的依赖管理

新建文件: .bowerrc


~ vi .bowerrc

{
    "directory": "app/bower_components"
}

新建文件: bower.json


~ vi bower.json

{
    "name": "angular-basic",
    "version": "0.0.1",
    "dependencies": {
        "angular": "~1.2.12-build.2226",
        "angular-route": "~1.2.12-build.2226"
    },
    "devDependencies": {
    }
}

运行bower,下载Angular依赖库


D:\workspace\javascript\angular-basic>bower install
bower angular-route#~1.2.12-build.2226           cached git://github.com/angular/bower-angular-route.git#1.2.13-build.2242
bower angular-route#~1.2.12-build.2226         validate 1.2.13-build.2242 against git://github.com/angular/bower-angular-route.git#~1.2.12-build.2226
bower angular#~1.2.12-build.2226                 cached git://github.com/angular/bower-angular.git#1.2.13-build.2242
bower angular#~1.2.12-build.2226               validate 1.2.13-build.2242 against git://github.com/angular/bower-angular.git#~1.2.12-build.2226
bower angular#1.2.13-build.2242+sha.e645f7c      cached git://github.com/angular/bower-angular.git#1.2.13-build.2242
bower angular#1.2.13-build.2242+sha.e645f7c    validate 1.2.13-build.2242 against git://github.com/angular/bower-angular.git#1.2.13-build.2242+sha.e645f7c
bower angular-route#~1.2.12-build.2226          install angular-route#1.2.13-build.2242
bower angular#~1.2.12-build.2226                install angular#1.2.13-build.2242

angular-route#1.2.13-build.2242 app\bower_components\angular-route
└── angular#1.2.13-build.2242

angular#1.2.13-build.2242 app\bower_components\angular

下载最新版本的angular和angular-route,类库存放在app/bower_components目录下面。

bower-angular

4. 配置Angularjs项目

  • 修改index.html: 在入口文件,页面模板
  • 修改welcome.html: 欢迎页
  • 修改app.js: 全局配置
  • 修改controller.js: 控制器

修改index.html


<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="utf-8">
<title>Angular-basic</title>
<meta name="description" content="Copyright http://blog.fens.me">
<link rel="stylesheet" href="styles/main.css">
</head>
<body ng-app="app">

<ul>
<li><a href="http://blog.fens.me/angularjs-express3-bootstrap3/">快速搭建Web环境 Angularjs + Express3 + Bootstrap3</a></li>
<li>http://blog.fens.me/angularjs-express3-bootstrap3</li>
</ul>

<div ng-view></div>

<script src="bower_components/angular/angular.min.js"></script>
<script src="bower_components/angular-route/angular-route.min.js"></script>
<script src="scripts/angular/app.js"></script>
<script src="scripts/angular/controllers.js"></script>

</body>
</html>

修改welcome.html


Welcome {{ username }}

修改app.js


'use strict';

var app = angular.module('app', ['ngRoute']);

app.config(['$routeProvider', '$locationProvider', function ($routeProvider, $locationProvider) {
    $routeProvider
        .when('/', {templateUrl: '/views/tpl/welcome.html', controller: 'WelcomeCtrl'})
        .otherwise({redirectTo: '/'});
    $locationProvider.html5Mode(true);
}]);

修改controller.js


'use strict';

function WelcomeCtrl($scope){
    $scope.username = 'Conan_Z';
}

重新启动node,查看Angular项目。


D:\workspace\javascript\angular-basic>node app.js
Express server listening on port 3000
GET / 304 8ms
GET /styles/main.css 304 3ms
GET /scripts/angular/app.js 304 6ms
GET /scripts/angular/controllers.js 304 11ms
GET /bower_components/angular-route/angular-route.min.js 200 18ms - 3.82kb
GET /bower_components/angular/angular.min.js 200 19ms - 98.03kb
GET /views/tpl/welcome.html 304 9ms
GET /bower_components/angular-route/angular-route.min.js.map 200 21ms - 9.61kb
GET /bower_components/angular/angular.min.js.map 200 26ms - 264.16kb

界面显示:

angular-start

index.html中配置的链接已经显示,同时welcome.html页面中配置的Welcome Conan_Z,也显示出来了。关于路由和模板配置,请参考文章:AngularJS路由和模板

5. 增加Bootstrap

接下来,增加Bootstrap-v3,让界面好看起来。我们还是有bower来管理Bootstrap的依赖。

通过命令行,增加类库,并写入的bower.json文件


D:\workspace\javascript\angular-basic>bower install bootstrap --save
D:\workspace\javascript\angular-basic>bower install angular-bootstrap --save
  • 修改index.html: 增加css, js的引用
  • 修改welcome.html: 增加bootstrap的效果

修改index.html


<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="utf-8">
<title>Angular-basic</title>
<meta name="description" content="Copyright http://blog.fens.me">
<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="styles/main.css">
</head>
<body ng-app="app">

<div class="container">
<h2 class="text-primary">
<a href="http://blog.fens.me/angularjs-express3-bootstarp3/">快速搭建Web环境 Angularjs + Express3 + Bootstarp3</a>
</h2>
<p>http://blog.fens.me/angularjs-express3-bootstarp3</p>

<div class="row">
<div class=".col-lg-12">
<div ng-view></div>
</div>
</div>
</div>

<script src="bower_components/angular/angular.min.js"></script>
<script src="bower_components/bootstrap/dist/js/bootstrap.min.js"></script>
<script src="bower_components/angular-route/angular-route.min.js"></script>
<script src="bower_components/angular-bootstrap/ui-bootstrap-tpls.min.js"></script>
<script src="scripts/angular/app.js"></script>
<script src="scripts/angular/controllers.js"></script>

</body>
</html>

修改welcome.html


<hr/>
<form class="form-inline" role="form">
<div class="form-group">
<label>Welcome</label>
<input type="text" class="form-control" ng-model="username" placeholder="Enter email">
</div>
</form>
<p> {{ username }}</p>

angular-bootstrap

这样就用手动的方式的搭建了:Angularjs + Express3 + Bootstrap3的组合。

6. 完整的项目

项目代码已上传的github,项目地址: https://github.com/bsspirit/angular-basic

项目下载及安装


git clone https://github.com/bsspirit/angular-basic
npm install
bower install

项目运行


node app.js

当然,对于大型的Angular项目,我们可以选择用Yeoman的种子构建,请参考文章:AngularJS从构建项目开始

但有时Yeoman的项目,更新不够及时,比如bootstrap已到v3了,种子项目还是bootstrap的v2,而且v3不兼容v2。这时也许手动构建自己的项目,才是更好的选择。

转载请注明出处:
http://blog.fens.me/angularjs-express3-bootstrap3/

打赏作者

Stylus让CSS也能编程

从零开始nodejs系列文章,将介绍如何利Javascript做为服务端脚本,通过Nodejs框架web开发。Nodejs框架是基于V8的引擎,是目前速度最快的Javascript引擎。chrome浏览器就基于V8,同时打开20-30个网页都很流畅。Nodejs标准的web开发框架Express,可以帮助我们迅速建立web站点,比起PHP的开发效率更高,而且学习曲线更低。非常适合小型网站,个性化网站,我们自己的Geek网站!!

关于作者

  • 张丹(Conan), 程序员Java,R,PHP,Javascript
  • weibo:@Conan_Z
  • blog: http://blog.fens.me
  • email: bsspirit@gmail.com

转载请注明出处:
http://blog.fens.me/nodejs-stylus-css/

stylus

前言

到目前为止,前端开发三个最重要的部分HTML, Javascript, CSS中,不管是文档还是工具,对于CSS的支持是最少的。在HTML5+CSS3的时代,我们要更加懂CSS,要让CSS也成为开发的利器。

对于开发来说,CSS的弱点在于静态化。我们不得不背CSS的各种规范,然后一字一句的编写文件。我们需要一个真正能提高开发效率的工具,LESS, SASS都在这方面做了一些贡献。

Stylus比LESS更强大,而且基于nodejs比SASS更符合我们的思路。

目录

  1. Stylus介绍
  2. Stylus安装
  3. Stylus语法
  4. 在Express3项目中使用Stylus
  5. 基于grunt自动处理

1. Stylus介绍

Stylus是一个CSS的预处理框架,Stylus是一种新型语言,可以创建健壮的、动态的、富有表现力的CSS。

什么是CSS预处理技术?CSS预处理技术,是指用一种新语言用来为CSS 增加可编程的的特性,无需考虑浏览器的兼容性问题。你可以在 CSS 中使用变量、简单的程序逻辑、函数等等在编程语言中的一些基本技巧,可以让你的 CSS 更见简洁,适应性更强。

Stylus默认使用 .styl 的作为文件扩展名,支持多样性的CSS语法。

2. Stylus安装

系统环境:

win7 64bit, node v0.10.5, npm 1.2.19

我们直接基于一个Express3的项目进行安装。express的入门,请参考文章:Nodejs开发框架Express3.0开发手记–从零开始

~ D:\workspace\javascript>express -e nodejs-stylus
~ D:\workspace\javascript>cd nodejs-stylus && npm install

~ D:\workspace\javascript\nodejs-stylus>npm install stylus
stylus@0.37.0 node_modules\stylus
├── debug@0.7.2
├── cssom@0.2.5
├── mkdirp@0.3.5
└── sax@0.5.5

配置Stylus的测试环境

增加两个文件:

  • stylus.js: 启动命令,渲染stylus的文件,在命令打印渲染后的结果
  • stylus.styl: stylus可编程的样式文件

增加stylus.js文件


~ vi stylus.js

var stylus = require("stylus"),
    str = require("fs").readFileSync("public/stylus/stylus.styl", "utf8");

stylus.render(str,function (err, css) {
    if (err) throw err;
    console.log(css);
});

增加public/stylus/stylus.styl文件


~ vi public/stylus/stylus.styl

border-radius()
  -webkit-border-radius: arguments
  -moz-border-radius: arguments
  border-radius: arguments

body a
  display inline-block
  padding: 2px 5px
  font: 12px/1.4 Arial, sans-serif
  background: black
  color: #eee
  border-radius: 2px

form input
  padding: 5px
  border: 1px solid
  border-radius: 5px

运行stylus.js的命令


~ D:\workspace\javascript\nodejs-stylus>node stylus.js

body a {
  display: inline-block;
  padding: 2px 5px;
  font: 12px/1.4 Arial, sans-serif;
  background: #000;
  color: #eee;
  -webkit-border-radius: 2px;
  -moz-border-radius: 2px;
  border-radius: 2px;
}
form input {
  padding: 5px;
  border: 1px solid;
  -webkit-border-radius: 5px;
  -moz-border-radius: 5px;
  border-radius: 5px;
}

我们看到了stylus的渲染效果,通过可编程的stylus.styl文件,生成标准的CSS的样式。接下来,我们继续看Stylus的基本使用。

3. Stylus语法

1). 选择器(Selectors)


body
  color white
==> 
body {
  color: #fff;
}

pad(n)
  padding (- n)
body
  pad(5px)
==> 
body {
  padding: -5px;
}

foo bar baz,
form input,
> a
  border 1px solid
==> 
foo bar baz,
form input,
> a {
  border: 1px solid;
}

textarea
input
  color #A7A7A7
  &:hover
    color #000
==> 
textarea,
input {
  color: #a7a7a7;
}
textarea:hover,
input:hover {
  color: #000;
}

2). 变量(Variables)


font-size = 14px
body
  font font-size Arial, sans-seri
==>
body {
  font: 14px Arial, sans-seri;
}

#logo
  position: absolute
  top: 50%
  left: 50%
  width: 150px
  height: 80px
  margin-left: -(@width / 2)
  margin-top: -(@height / 2)
==>
#logo {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 150px;
  height: 80px;
  margin-left: -75px;
  margin-top: -40px;
}

body
  color: red
  ul
    li
      color: blue
      a
        background-color: @color
==>
body {
  color: #f00;
}
body ul li {
  color: #00f;
}
body ul li a {
  background-color: #00f;
}

3). 插值(Interpolation)


vendor(prop, args)
  -webkit-{prop} args
  -moz-{prop} args
  {prop} args
button
  border-radius 1px 2px / 3px 4px
==>
button {
  border-radius: 1px 2px/3px 4px;
}

table
  for row in 1 2 3 4 5
    tr:nth-child({row})
      height: 10px * row
==>
table tr:nth-child(1) {
  height: 10px;
}
table tr:nth-child(2) {
  height: 20px;
}
table tr:nth-child(3) {
  height: 30px;
}
table tr:nth-child(4) {
  height: 40px;
}
table tr:nth-child(5) {
  height: 50px;
}

4). 运算符(Operators)


body
  n = 5
  foo: (n)em
  foo: (n)%
  foo: (n * 5)px
  foo: unit(n + 5, '%')
==>
body {
  foo: 5em;
  foo: 5%;
  foo: 25px;
  foo: 10%;
}

a = 15px
div
  if a is defined
    padding 5px
  if b
    margin 5px
==>
div {
  padding: 5px;
  margin: 5px;
}

pad(types = padding, n = 5px)
  if padding in types
    padding n
  if margin in types
    margin n
body
  pad()
body
  pad(margin)
body
  pad(padding margin, 10px)
==>
body {
  padding: 5px;
}
body {
  margin: 5px;
}
body {
  padding: 10px;
  margin: 10px;
}

5). 混合书写(Mixins)


border-radius(n)
  -webkit-border-radius n
  -moz-border-radius n
  border-radius n
form input[type=button]
  border-radius 5px
==>
form input[type=button] {
  -webkit-border-radius: 5px;
  -moz-border-radius: 5px;
  border-radius: 5px;
}

support-for-ie ?= true
opacity(n)
  opacity n
  if support-for-ie
    filter unquote('progid:DXImageTransform.Microsoft.Alpha(Opacity=' + round(n * 100) + ')')
#logo
  &:hover
    opacity 0.5
==>
#logo:hover {
  opacity: 0.5;
  filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=50);
}

inline-list()
  li
    display inline
comma-list()
  inline-list()
  li
    &:after
      content ', '
    &:last-child:after
      content ''
ul
  comma-list()
==>
ul li {
  display: inline;
}
ul li:after {
  content: ', ';
}
ul li:last-child:after {
  content: '';
}

6). 方法(Functions)


invoke(a, b, fn)
  fn(a, b)
add(a, b)
  a + b
body
  padding invoke(5, 10, add)
  padding invoke(5, 10, sub)
==>
body {
  padding: 15;
  padding: fn(5, 10);
}

get(hash, key)
  return pair[1] if pair[0] == key for pair in hash
div
  hash = (one 1) (two 2) (three 3)
  padding:get(hash, two)
==>
div {
  padding: 2;
}

7). 关键字参数(Keyword Arguments)


body {
   color: rgba(255, 200, 100, 0.5);
   color: rgba(red: 255, green: 200, blue: 100, alpha: 0.5);
   color: rgba(alpha: 0.5, blue: 100, red: 255, 200);
   color: rgba(alpha: 0.5, blue: 100, 255, 200);
}
==>
body {
  color: rgba(255,200,100,0.5);
  color: rgba(255,200,100,0.5);
  color: rgba(255,200,100,0.5);
  color: rgba(255,200,100,0.5);
}

//查看函数或混合书写中接受的参数
p(rgba)
==>
inspect: rgba(red, green, blue, alpha)

8). 内置方法(Built-in Functions)


red(#c00)
green(#0c0)
blue(#00c)
alpha(rgba(0,0,0,0.3))
dark(black)
light(white)
hue(hsla(50deg, 100%, 80%))
saturation(hsla(50deg, 100%, 80%))
lightness(hsla(50deg, 100%, 80%))

nums = 1 2
push(nums, 3, 4, 5)

nums= 4 5
unshift(nums, 3, 2, 1)

pairs = (one 1) (two 2) (three 3)
keys(pairs)
values(pairs)

typeof(12)
typeof(#fff)

unit(15in)
unit(10%,px)

match('^foo(bar)?', foo)
match('^foo(bar)?', foobar)

abs(-5px)
ceil(5.5in)
floor(5.6px)
round(5.5px)
min(1, 5)
max(1, 5)
even(6px)
odd(5mm)
sum(1 2 3)
avg(1 2 3)
join(',', 1 2 3)

hsla(10, 50, 30)
rgba(255,0,0,1)
rgb(255,204,0)
#fc08
lighten(#2c2c2c, 30)
darken(#D62828, 30)
desaturate(#f00, 40%)
saturate(#c33, 40%)
invert(#d62828)

unquote("sans-serif")
s('bar()')
s('bar(%s)', 'baz')
warn("oh noes!")
error('add() expects units')

op = '+'
operate(op, 15, 5)
last(1 2 3)
length((1 2 3 4))
p('test')
opposite-position(right)

image-size('tux.png')
add-property('bar', 1 2 3)
stop(50%, orange)

9). 多参数(Rest Params)


box-shadow(args...)
  -webkit-box-shadow args
  -moz-box-shadow args
  box-shadow args
#login
  box-shadow 1px 2px 5px #eee
==>
#login {
  -webkit-box-shadow: 1px 2px 5px #eee;
  -moz-box-shadow: 1px 2px 5px #eee;
  box-shadow: 1px 2px 5px #eee;
}

box-shadow()
  -webkit-box-shadow arguments
  -moz-box-shadow arguments
  box-shadow arguments
#login
  box-shadow #ddd 1px 1px, #eee 2px 2px
==>
#login {
  -webkit-box-shadow: #ddd 1px 1px, #eee 2px 2px;
  -moz-box-shadow: #ddd 1px 1px, #eee 2px 2px;
  box-shadow: #ddd 1px 1px, #eee 2px 2px;
}

10). 注释(Comments)


// one line
==>

/*
 * multi-line
 */
==>
/*
 * multi-line
 */

/*!
 * multi-line buffered
 */
==>
/*
 * multi-line buffered
 */

11). 条件(Conditionals)


box(x, y, margin-only = false)
  if margin-only
    margin y x
  else
    padding y x
div
  box(5px, 10px, true)
==>
div {
  margin: 10px 5px;
}

negative(n)
  error('invalid num') unless n is a 'unit'
  return yes if n < 0   no negative(15) ==> no
negative('str')
==> 
invalid num
at negative() (stylus:173)

12). 迭代(Iteration)


apply(props)
  props = arguments if length(arguments) > 1
  for prop in props
    {prop[0]} prop[1]
body
  apply(one 1, two 2, three 3)
==>
body {
  one: 1;
  two: 2;
  three: 3;
}  
body
  list = (one 1) (two 2) (three 3)
  apply(list)
==>
body {
  one: 1;
  two: 2;
  three: 3;
}

first-even(nums)
  return n if n % 2 == 0 for n in nums
div
    padding first-even(1 3 5 5 6 3 2)px
==>
div {
  padding: 6px;
}

13). @import,@media,@font-face, @keyframes, @extend,@css


//样式文件导入
@import "reset.css" //原始css导入
@import 'mixins/vendor'//stylus动态导入

//与标准CSS一样
@media print 
  #header
  #footer
    display none

//与标准CSS一样
@font-face
  font-family Geo
  font-style normal
  src url(fonts/geo_sans_light/GensansLight.ttf)

//@keyframes没用过不知道干什么的!汗!
vendors = official
@keyframes foo {
  from {
    color: black
  }
  to {
    color: white
  }
}
==>
@keyframes foo {
  0% {
    color: #000;
  }
  100% {
    color: #fff;
  }
}

//@extend样式
.message {
  padding: 10px;
  border: 1px solid #eee;
}
.warning {
  @extend .message;
  color: #E2E21E;
}
==>
.message,
.warning {
  padding: 10px;
  border: 1px solid #eee;
}
.warning {
  color: #e2e21e;
}

//@css不转义,直接输出字符串
@css {
  body {
    font: 14px;
  }
}
==>
body {
  font: 14px;
}

14). 自检API(Introspection API)


reset()
  if mixin == 'root'
    got
      root true
  else if mixin
    got 'a mixin'
  else
    'not a mixin'
reset()
body
  reset()
  foo reset()
==>
got {
  root: true;
}
body {
  got: 'a mixin';
  foo: 'not a mixin';
}

4. 在Express3项目中使用Stylus

刚开我们一直在讲Stylus的语法,下面我们配置到项目里试一下真正地效果。

实现自动编译stylus到css,我们还要借助于grunt。

~ D:\workspace\javascript\nodejs-stylus>npm install grunt-contrib-stylus --save-dev

需要修改的文件:

  • Gruntfile.js:grunt的配置文件
  • demo.styl:stylus的脚本
  • index.ejs:ejs模板文件

配置grunt的stylus任务

~ vi Gruntfile.js
module.exports = function (grunt) {
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        stylus: {
            compile: {
                files: {
                    'public/stylesheets/demo.css': ['public/stylus/demo.styl']
                }
            }
        }
    });

    grunt.loadNpmTasks('grunt-contrib-stylus');
    grunt.registerTask('default', ['stylus']);
}

增加一个demo.styl的文件

~ vi public/stylus/demo.styl
body
  color red
pad(n)
  padding (- n)
body
  pad(5px)

修改index.ejs文件


~ vi views/index.ejs
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<link rel='stylesheet' href='/stylesheets/demo.css' />
</head>
<body>
<h1><%= title %></h1>
<p>Welcome to <%= title %></p>
</body>
</html>

运行grunt命令:

grunt stylus

自动生成一个文件:
public/stylesheets/demo.css

打开浏览器:localhost:3000
stylus1

5. 基于grunt自动处理

接下来通过grunt wath配置自动更新。

下载grunt-contrib-watch插件

~ D:\workspace\javascript\nodejs-stylus>npm install grunt-contrib-watch --save-dev

修改Gruntfile.js文件,增加watch的配置


module.exports = function (grunt) {
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        stylus: {
            compile: {
                files: {
                    'public/stylesheets/demo.css': ['public/stylus/*.styl']
                }
            }
        },
        watch: {
            scripts: {
                files: ['public/stylus/demo.styl'],
                tasks: ['stylus']
            }
        }
    });

    grunt.loadNpmTasks('grunt-contrib-stylus');
    grunt.loadNpmTasks('grunt-contrib-watch');

    grunt.registerTask('default', ['stylus']);
}

接下来,我们启动两个命令窗口,一个用于grunt watch,一个用于supervisor.


~ D:\workspace\javascript\nodejs-stylus>grunt watch
~ D:\workspace\javascript\nodejs-stylus>supervisor app.js

stylus2

修改demo.styl


~ vi public/stylus/demo.styl

body
  color red
  background gray
pad(n)
  padding (- n)
body
  pad(5px)

点击保存后。
grunt watch日志


OK
>> File "public\stylus\demo.styl" changed.

Running "stylus:compile" (stylus) task
File public\stylesheets\demo.css created.

Done, without errors.
Completed in 0.825s at Mon Sep 02 2013 15:06:21 GMT+0800 (中国标准时间) - Waiting...

supervisro日志


GET / 200 0ms - 205b
GET /stylesheets/demo.css 200 13ms - 57b

刷新页面: http://localhost:3000/
stylus3

我们实现了,stylus与express3开发的自动集成!

到目前为止,就像学一门新语言一样,还是晕头转向!Stylus很强大,但是能不能真正被用起来,还需要我们更多的探索。

5. 在线体验和资源

作者提供了在线体验的应用:http://learnboost.github.io/stylus/try.html

中文资源:http://www.zhangxinxu.com/jq/stylus/

转载请注明出处:
http://blog.fens.me/nodejs-stylus-css/

打赏作者

Nginx反向代理Nodejs – log4js日志IP显示错误

从零开始nodejs系列文章

从零开始nodejs系列文章,将介绍如何利Javascript做为服务端脚本,通过Nodejs框架web开发。Nodejs框架是基于V8的引擎,是目前速度最快的Javascript引擎。chrome浏览器就基于V8,同时打开20-30个网页都很流畅。Nodejs标准的web开发框架Express,可以帮助我们迅速建立web站点,比起PHP的开发效率更高,而且学习曲线更低。非常适合小型网站,个性化网站,我们自己的Geek网站!!

关于作者

张丹(Conan), 程序员Java,R,PHP,Javascript
weibo:@Conan_Z
blog: http://blog.fens.me
email: bsspirit@gmail.com

转载请注明出处:
http://blog.fens.me/nodejs-nginx-log4js/

前言

为提高web应用的吞吐量,我们一般都用nginx做负载均衡器,再反向代理到真正的web服务器。像JAVA服务可以做成Nginx+Tomcat, PHP用Nginx+Spawn-fcgi, 当然nodejs服务,我们也可以做相同的架构Ngnix+Node。

我使用nginx+nodejs+log4js时,发现log4js日志的remote_addr项,总是显示127.0.0.1。本文将重点解释如何通过修改log4js的代码,解决这个问题。

我已经提交这个问题到log4js-node的issue
https://github.com/nomiddlename/log4js-node/issues/139

log4js-bug

文章目录:

  1. 配置nginx反向代理
  2. log4js做为web日志输出
  3. log4js日志ip显示错误及修复

1. 配置nginx反向代理

本地启动nodejs : node app.js ,默认3000端口。

安装nginx

~ sudo apt-get install nginx

进入nginx配置目录

~ cd /etc/nginx/sites-enabled
/etc/nginx/sites-enabled

新建一个配置文件moive

~ vi moive

#moive.me
upstream nodejs__upstream {
        server 127.0.0.1:3000;
        #server 127.0.0.1:3001;
        keepalive 64;
}
server {
        listen 80;
        server_name www.moive.me moive.me;
        access_log /var/log/nginx/moiveme.log;
        location / {
                proxy_set_header   X-Real-IP            $remote_addr;
                proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
                proxy_set_header   Host                   $http_host;
                proxy_set_header   X-NginX-Proxy    true;
                proxy_set_header   Connection "";
                proxy_http_version 1.1;
                proxy_pass         http://nodejs__upstream;
        }
}

对配置项的解释:

  • listen,监听端口80
  • server_name , 监听域名 www.moive.me moive.me
  • access_log ,nginx日志输出 /var/log/nginx/moiveme.log
  • proxy_pass ,反向代理转发 http://nodejs__upstream;
  • 通过upstream nodejs__upstream 可以配置多台nodejs节点,做负载均衡。
  • keepalive ,设置存活时间。如果不设置可能会产生大量的timewait。

重新启动nginx

~ sudo /etc/init.d/nginx restart

通过浏览器访问web站点
http://moive.me

2. log4js做为web日志输出

log4js的配置与使用,请查看 玩转Nodejs日志管理log4js

查看控制台日志,发现一个问题,客户端IP记录值都是127.0.0.1。这个是我们不期待的。

GET / 304 24ms
[2013-06-20 06:55:39.405] [WARN] [default] - 127.0.0.1 - - "GET / HTTP/1.1" 304 - "" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.110 Safari/537.36"
GET /css/my.css 304 5ms
[2013-06-20 06:55:39.646] [WARN] [default] - 127.0.0.1 - - "GET /css/my.css HTTP/1.1" 304 - "http://moive.me/" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.110 Safari/537.36"
GET /css/bootstrap.min.css 304 4ms
[2013-06-20 06:55:39.651] [WARN] [default] - 127.0.0.1 - - "GET /css/bootstrap.min.css HTTP/1.1" 304 - "http://moive.me/" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.110 Safari/537.36"
GET /js/holder.js 304 3ms
[2013-06-20 06:55:39.652] [WARN] [default] - 127.0.0.1 - - "GET /js/holder.js HTTP/1.1" 304 - "http://moive.me/" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.110 Safari/537.36"
GET /js/jquery-1.9.1.min.js 304 4ms
[2013-06-20 06:55:39.653] [WARN] [default] - 127.0.0.1 - - "GET /js/jquery-1.9.1.min.js HTTP/1.1" 304 - "http://moive.me/" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.110 Safari/537.36"

在浏览器通过IP直接访问,http://50.116.27.194:3000/, 所有的客户端IP显示正常。

GET / 304 12ms
[2013-06-20 06:57:49.492] [WARN] [default] - 114.252.83.33 - - "GET / HTTP/1.1" 304 - "" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.110 Safari/537.36"
GET /js/holder.js 304 2ms
[2013-06-20 06:57:49.720] [WARN] [default] - 114.252.83.33 - - "GET /js/holder.js HTTP/1.1" 304 - "http://50.116.27.194:3000/" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.110 Safari/537.36"
GET /css/bootstrap.min.css 304 2ms
[2013-06-20 06:57:49.723] [WARN] [default] - 114.252.83.33 - - "GET /css/bootstrap.min.css HTTP/1.1" 304 - "http://50.116.27.194:3000/" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.110 Safari/537.36"
GET /css/my.css 304 2ms
[2013-06-20 06:57:49.724] [WARN] [default] - 114.252.83.33 - - "GET /css/my.css HTTP/1.1" 304 - "http://50.116.27.194:3000/" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.110 Safari/537.36"

我们把问题定位在nginx转发出现的。

在程序中打印客户端IP。

~ vi routes/index.js

        console.log("ip=>"+req.ip);
	console.log("REMOTE_ADDR=>"+req.get['REMOTE_ADDR']);
	console.log("HTTP_X_REAL_IP=>"+req.get['HTTP_X_REAL_IP']);
	console.log("x-real-ip=>"+req.get['x-real-ip']);
	console.log("x-forwarded-for=>"+req.get['x-forwarded-for']);

	var headers = req.headers;
	console.log("headers=>"+ (headers['x-real-ip'] || headers['x-forwarded-for']));
	console.log("headers x-real-ip=>"+headers['x-real-ip']);
	console.log("headers x-forwarded-for=>"+headers['x-forwarded-for']);

	console.log("====================================================");

	var proxy_ip=headers['x-real-ip'] || headers['x-forwarded-for']
	if(proxy_ip) return console.log("proxy"+proxy_ip);
	if(req.ip) return console.log(req.ip);

	console.log("====================================================");

http://moive.me, 通过nginx转发的输出结果:

[2013-06-20 07:01:48.731] [INFO] console - ip=>127.0.0.1
[2013-06-20 07:01:48.732] [INFO] console - REMOTE_ADDR=>undefined
[2013-06-20 07:01:48.732] [INFO] console - HTTP_X_REAL_IP=>undefined
[2013-06-20 07:01:48.732] [INFO] console - x-real-ip=>undefined
[2013-06-20 07:01:48.732] [INFO] console - x-forwarded-for=>undefined
[2013-06-20 07:01:48.732] [INFO] console - headers=>114.252.83.33
[2013-06-20 07:01:48.732] [INFO] console - headers x-real-ip=>114.252.83.33
[2013-06-20 07:01:48.732] [INFO] console - headers x-forwarded-for=>114.252.83.33
[2013-06-20 07:01:48.732] [INFO] console - ====================================================
[2013-06-20 07:01:48.733] [INFO] console - proxy114.252.83.33
[2013-06-20 07:01:48.733] [INFO] console - req.ip127.0.0.1

http://50.116.27.194:3000, 直接通过IP访问的输出结果

[2013-06-20 06:57:49.480] [INFO] console - ip=>114.252.83.33
[2013-06-20 06:57:49.480] [INFO] console - REMOTE_ADDR=>undefined
[2013-06-20 06:57:49.480] [INFO] console - HTTP_X_REAL_IP=>undefined
[2013-06-20 06:57:49.481] [INFO] console - x-real-ip=>undefined
[2013-06-20 06:57:49.481] [INFO] console - x-forwarded-for=>undefined
[2013-06-20 06:57:49.481] [INFO] console - headers=>undefined
[2013-06-20 06:57:49.481] [INFO] console - headers x-real-ip=>undefined
[2013-06-20 06:57:49.481] [INFO] console - headers x-forwarded-for=>undefined
[2013-06-20 06:57:49.481] [INFO] console - ====================================================
[2013-06-20 06:57:49.482] [INFO] console - req.ip114.252.83.33

我们已经发现,通过nginx转发的客户端IP是不能直接使用express3框架中req.ip获得的,而是需要通过req.headers[‘x-real-ip’]和req.headers[‘x-forwarded-for’]才能得到。

3. log4js日志ip显示错误及修复

下面我们要再查一下log4js代码,看看他是怎么实现的,为什么会出现IP的错误。

打开 log4js/lib/connect-logger.js 118行

~ vi connect-logger.js
 function format(str, req, res) {
        return str
        .replace(':url', req.originalUrl)
        .replace(':method', req.method)
        .replace(':status', res.__statusCode || res.statusCode)
        .replace(':response-time', res.responseTime)
        .replace(':date', new Date().toUTCString())
        .replace(':referrer', req.headers['referer'] || req.headers['referrer'] || '')
        .replace(':http-version', req.httpVersionMajor + '.' + req.httpVersionMinor)
        .replace(':remote-addr',req.socket && (req.socket.remoteAddress || (req.socket.socket && req.socket.socket.remoteAddress)))
        .replace(':user-agent', req.headers['user-agent'] || '')
        .replace(':content-length', (res._headers && res._headers['content-length']) || (res.__headers && res.__headers['Content-Length']) || '-')
        .replace(/:req\[([^\]]+)\]/g, function(_, field){ return req.headers[field.toLowerCase()]; })
        .replace(/:res\[([^\]]+)\]/g, function(_, field){
                return res._headers
                ? (res._headers[field.toLowerCase()] || res.__headers[field])
                : (res.__headers && res.__headers[field]);
        });
 }

我们发现他的代码和express3的req.ip实现的代码是类似的,这样是不识别通过代理程序转发的客户端IP。

.replace(':remote-addr',req.socket && (req.socket.remoteAddress || (req.socket.socket && req.socket.socket.remoteAddress)))

下面对这行代码进行修改。

.replace(':remote-addr', req.headers['x-forwarded-for'] || req.connection.remoteAddress)

先检查不是转发的IP,再取默认IP。

重起nodejs服务器

浏览器访问测试。

  • http://moive.me
  • http://50.116.27.194:3000
[2013-06-20 07:12:58.706] [WARN] [default] - 114.252.83.33 - - "GET / HTTP/1.1" 304 - "http://moive.me/" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.110 Safari/537.36"
GET /css/bootstrap.min.css 304 5ms
[2013-06-20 07:12:58.949] [WARN] [default] - 114.252.83.33 - - "GET /css/bootstrap.min.css HTTP/1.1" 304 - "http://moive.me/" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.110 Safari/537.36"
GET /css/my.css 304 3ms
[2013-06-20 07:12:59.153] [WARN] [default] - 114.252.83.33 - - "GET /css/my.css HTTP/1.1" 304 - "http://moive.me/" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.110 Safari/537.36"
GET /js/jquery-1.9.1.min.js 304 2ms
[2013-06-20 07:12:59.156] [WARN] [default] - 114.252.83.33 - - "GET /js/jquery-1.9.1.min.js HTTP/1.1" 304 - "http://moive.me/" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.110 Safari/537.36"

两种访问测试都正常,解决了log4js通过nginx反向代理,日志IP显示错误的问题。

转载请注明出处:
http://blog.fens.me/nodejs-nginx-log4js/

打赏作者