• Posts tagged "nodejs"
  • (Page 4)

Blog Archives

Hexo在github上构建免费的Web应用

从零开始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/hexo-blog-github/

hexo-github

前言

很多次想把博客从wordpress迁移到github,为此还特意学了一下Ruby和Jekyll。但由于迁移过程过于复杂,我又没有很多时间,这个计划就被搁置了。慢慢地文章积累越来越多了,更没有时间来整理了,所以就先用着wordpress吧。

不过,了迁移博客我也有一些新的发现。hexo,一个基于Node的博客框架,同样可以实现基于github的博客,而且更轻更快,更适合Node的开发程序员。

目录

  1. Hexo介绍
  2. Hexo安装
  3. Hexo的使用
  4. 发布到项目到github
  5. 替换皮肤
  6. 配置常用插件

1. Hexo介绍

Hexo 是一个简单地、轻量地、基于Node的一个静态博客框架。通过Hexo我们可以快速创建自己的博客,仅需要几条命令就可以完成。

发布时,Hexo可以部署在自己的Node服务器上面,也可以部署github上面。对于个人用户来说,部署在github上好处颇多,不仅可以省去服务器的成本,还可以减少各种系统运维的麻烦事(系统管理、备份、网络)。所以,基于github的个人站点,正在开始流行起来….

Hexo的官方网站:http://hexo.io/ ,也是基于Github构建的网站。

2. Hexo安装

系统环境:

  • win7 64bit
  • node v0.10.5
  • npm 1.2.19

Hexo安装,要用全局安装,加-g参数。


D:\> npm install -g hexo

查看hexo的版本


D:\> hexo version
hexo: 2.5.5
os: Windows_NT 6.1.7601 win32 x64
http_parser: 1.0
node: 0.10.5
v8: 3.14.5.8
ares: 1.9.0-DEV
uv: 0.10.5
zlib: 1.2.3
modules: 11
openssl: 1.0.1e

安装好后,我们就可以使用Hexo创建项目了。


D:\workspace\javascript> hexo init nodejs-hexo 
[info] Creating file: source/_posts/hello-world.md
[info] Creating file: package.json
[info] Creating file: .gitignore
[info] Copying file: _config.yml
[info] Copying file: scaffolds/draft.md
[info] Copying file: scaffolds/page.md
[info] Copying file: scaffolds/photo.md
[info] Copying file: scaffolds/post.md
[info] Creating folder: source/_drafts
[info] Creating folder: scripts
[info] Copying theme data...
[info] Initialization has been done. Start blogging with Hexo!

我们看到当前在目录下,出现了一个文件夹,包括初始化的文件。

进入目录,并启动Hexo服务器。


# 进入目录
D:\workspace\javascript>cd nodejs-hexo

# 启动hexo服务器
D:\workspace\nodejs-hexo>hexo server
[info] Hexo is running at http://localhost:4000/. Press Ctrl+C to stop.

这时端口4000被打开了,我们能过浏览器打开地址,http://localhost:4000/ 。

hexo-web

出现了默认的网页界面,是不是很容易呢!!

3. Hexo的使用

接下来,我们要对Hexo做更全面的了解,才能做出个性化一的博客。

3.1 目录和文件

先来看一下,默认生成了哪些东西。

hexo-folder

  • scaffolds 脚手架,也就是一个工具模板
  • scripts 写文件的js,扩展hexo的功能
  • source 存放博客正文内容
  • source/_drafts 草稿箱
  • source/_posts 文件箱
  • themes 存放皮肤的目录
  • themes/landscape 默认的皮肤
  • _config.yml 全局的配置文件
  • db.json 静态常量

在这里,我们每次用到的就是_posts目录里的文件,而_config.yml文件和themes目录是第一次配置好就行了。

_posts目录:Hexo是一个静态博客框架,因此没有数据库。文章内容都是以文本文件方式进行存储的,直接存储在_posts的目录。Hexo天生集成了markdown,我们可以直接使用markdown语法格式写博客,例如:hello-world.md。新增加一篇文章,就在_posts目录,新建一个xxx.md的文件。

themes目录:是存放皮肤的,包括一套Javascript+CSS样式和基于EJS的模板设置。通过在themes目录下,新建一个子目录,就可以创建一套新的皮肤,当然我们也可以直接在landscape上面修改。

3.2 全局配置

_config.yml是全局的配置文件:很多的网站配置都在这个文件中定义。

  • 站点信息: 定义标题,作者,语言
  • URL: URL访问路径
  • 文件目录: 正文的存储目录
  • 写博客配置:文章标题,文章类型,外部链接等
  • 目录和标签:默认分类,分类图,标签图
  • 归档设置:归档的类型
  • 服务器设置:IP,访问端口,日志输出
  • 时间和日期格式: 时间显示格式,日期显示格式
  • 分页设置:每页显示数量
  • 评论:外挂的Disqus评论系统
  • 插件和皮肤:换皮肤,安装插件
  • Markdown语言:markdown的标准
  • CSS的stylus格式:是否允许压缩
  • 部署配置:github发布

查看文件:_config.yml


# Hexo Configuration
## Docs: http://hexo.io/docs/configuration.html
## Source: https://github.com/tommy351/hexo/

# 站点信息
title: Hexo博客
subtitle: 新的开始
description: blog.fens.me
author: bsspirit
email: bsspirit@gmail.com
language: zh-CN

# URL
## If your site is put in a subdirectory, set url as 'http://yoursite.com/child' and root as '/child/'
url: http://blog.fens.me
root: /
permalink: :year/:month/:day/:title/
tag_dir: tags
archive_dir: archives
category_dir: categories
code_dir: downloads/code

# 文件目录
source_dir: source
public_dir: public

# 写博客配置
new_post_name: :title.md # File name of new posts
default_layout: post
auto_spacing: false # Add spaces between asian characters and western characters
titlecase: false # Transform title into titlecase
external_link: true # Open external links in new tab
max_open_file: 100
multi_thread: true
filename_case: 0
render_drafts: false
post_asset_folder: false
highlight:
  enable: true
  line_number: true
  tab_replace:

# 目录和标签
default_category: uncategorized
category_map:
tag_map:

# 归档设置
## 2: Enable pagination
## 1: Disable pagination
## 0: Fully Disable
archive: 2
category: 2
tag: 2

# 服务器设置
## Hexo uses Connect as a server
## You can customize the logger format as defined in
## http://www.senchalabs.org/connect/logger.html
port: 4000
server_ip: 0.0.0.0
logger: false
logger_format:

# 时间和日期格式
## Hexo uses Moment.js to parse and display date
## You can customize the date format as defined in
## http://momentjs.com/docs/#/displaying/format/
date_format: MMM D YYYY
time_format: H:mm:ss

# 分页设置
## Set per_page to 0 to disable pagination
per_page: 10
pagination_dir: page

# 评论
disqus_shortname:

# 插件和皮肤
## Plugins: https://github.com/tommy351/hexo/wiki/Plugins
## Themes: https://github.com/tommy351/hexo/wiki/Themes
theme: landscape
exclude_generator:

# Markdown语法
## https://github.com/chjj/marked
markdown:
  gfm: true
  pedantic: false
  sanitize: false
  tables: true
  breaks: true
  smartLists: true
  smartypants: true

# CSS的stylus格式
stylus:
  compress: false

# 部署配置
## Docs: http://hexo.io/docs/deployment.html
deploy:
  type:

3.3 命令行使用

查看命令行帮助


D:\> hexo help
Usage: hexo 

Commands:
  help      Get help on a command
  init      Create a new Hexo folder
  migrate   Migrate your site from other system to Hexo
  version   Display version information

Global Options:
  --config   Specify config file instead of using _config.yml
  --debug    Display all verbose messages in the terminal
  --safe     Disable all plugins and scripts
  --silent   Hide output on console

For more help, you can use `hexo help [command]` for the detailed information
or you can check the docs: http://hexo.io/docs/

命令行解释:

  • help 查看帮助信息
  • init 创建一个hexo项目
  • migrate 从其他系统向hexo迁移
  • version 查看hexo的版本
  • –config参数,指定配置文件,代替默认的_config.yml
  • –debug参数,调试模式,输出所有日志信息
  • –safe参数,安全模式,禁用所有的插件和脚本
  • –silent参数,无日志输出模式

3.4 创建新文章

接下来,我们开始新博客了,创建第一博客文章。Hexo建议通过命令行操作,当然你也可以直接在_posts目录下创建文件。

通过命令创建新文章


D:\workspace\javascript\nodejs-hexo>hexo new 新的开始
[info] File created at D:\workspace\javascript\nodejs-hexo\source\_posts\新的开始.md

在_posts目录下,就会生成文件:”新的开始.md”。

hexo-post

然后,我们编辑文件:”新的开始.md”,以markdown语法写文章,然后保存。


title: 新的开始
date: 2014-05-07 18:44:12
tags:
- 开始
- 我
- 日记
categories: 日志
---

这是**新的开始**,我用hexo创建了第一篇文章。

通过下面的命令,就可以创建新文章
```{bash}
D:\workspace\javascript\nodejs-hexo>hexo new 新的开始
[info] File created at D:\workspace\javascript\nodejs-hexo\source\_posts\新的开始.md
```

感觉非常好。

在命令行,启动服务器。


D:\workspace\javascript\nodejs-hexo>hexo server
[info] Hexo is running at http://localhost:4000/. Press Ctrl+C to stop.

通过浏览器打开, http://localhost:4000/ ,就出现了我们新写的文章。

hexo-index

同时,网页的右侧还会出现Categories(目录),Tags(标签),Tag Cloud(标签云)的显示。

3.5 文章的语法

我们在写文章时,有一些语法的要求。

语法包括3部分:

  • 基本信息:标题,发布日期,分类目录,标签,类型,固定发布链接
  • 正文:markdown语法和Swig语法(掌握一个就行)
  • 特殊标记:引用,链接,图片,代码块,iframe,youtube视频

3.5.1 基本信息

必须在文件的顶部,—的行之前的部分。如:


title: 新的开始
date: 2014-05-07 18:44:12
updated	: 2014-05-10 18:44:12
permalink: abc
tags:
- 开始
- 我
- 日记
categories:
- 日志
- 第一天

---

我们可以对刚才发的文章,做上面的修改,再看效果。

3.5.2 正文

hexo的正文要求使用markdown的语法,这里就不在多说,请自行查看markdwon的文档。

3.5.3 特殊标记

hexo对于一些有特殊标记 文字块,做了特殊的定义。

引用


# Swig语法
{% blockquote Seth Godin http://sethgodin.typepad.com/seths_blog/2009/07/welcome-to-island-marketing.html Welcome to Island Marketing %}
Every interaction is both precious and an opportunity to delight.
{% endblockquote %}

# Markdown语法
> Every interaction is both precious and an opportunity to delight.

代码块


# Swig语法
{% codeblock .compact http://underscorejs.org/#compact Underscore.js %}
.compact([0, 1, false, 2, ‘’, 3]);
=> [1, 2, 3]
{% endcodeblock %}

# Markdown语法
```{bash}
.compact([0, 1, false, 2, ‘’, 3]);
=> [1, 2, 3]
```

链接


{% link 粉丝日志 http://blog.fens.me true 粉丝日志 %}

# Markdown语法
[粉丝日志](http://blog.fens.me)

图片,对于本地图片,需要在source目录下面新建一个目录images,然后把图片放到目录中。


# Swig语法
{% img /images/fens.me.png 400 600 这是一张图片 %}

# Markdown语法
![这是一张图片](/images/fens.me.png)

在浏览器中看到效果。

hexo-content

我们发现Swig的语法比markdown语法有更多的配置项,可以让页面更丰富,下面显示完整的基于Swig代码。


title: 新的开始
date: 2014-05-07 18:44:12
permalink: abc
tags:
- 开始
- 我
- 日记
categories:
- 日志
- 第一天

---

这是**新的开始**,我用hexo创建了第一篇文章。

通过下面的命令,就可以创建新文章
```{bash}
D:\workspace\javascript\nodejs-hexo>hexo new 新的开始
[info] File created at D:\workspace\javascript\nodejs-hexo\source\_posts\新的开始.md
```

感觉非常好。


## 引用
{% blockquote Seth Godin http://sethgodin.typepad.com/seths_blog/2009/07/welcome-to-island-marketing.html Welcome to Island Marketing %}
Every interaction is both precious and an opportunity to delight.
{% endblockquote %}

## 代码块
{% codeblock .compact http://underscorejs.org/#compact Underscore.js %}
.compact([0, 1, false, 2, ‘’, 3]);
=> [1, 2, 3]
{% endcodeblock %}

## 链接
{% link 粉丝日志 http://blog.fens.me true 粉丝日志 %}

## 图片
{% img /images/fens.me.png 400 600 这是一张图片 %}

4. 发布到项目到github

4.1 静态化处理

写完了文章,我们就可以发布了。要说明的一点是hexo的静态博客框架,那什么是静态博客呢?静态博客,是只包含html, javascript, css文件的网站,没有动态的脚本。虽然我们是用Node进行的开发,但博客的发布后就与Node无关了。在发布之前,我们要通过一条命令,把所有的文章都做静态化处理,就是生成对应的html, javascript, css,使得所有的文章都是由静态文件组成的。

静态化命令


D:\workspace\javascript\nodejs-hexo>hexo generate
[info] Files loaded in 0.895s
[create] Public: js\script.js
[create] Public: css\fonts\fontawesome-webfont.svg
[create] Public: css\fonts\FontAwesome.otf
[create] Public: css\fonts\fontawesome-webfont.ttf
[create] Public: css\fonts\fontawesome-webfont.eot
[create] Public: css\fonts\fontawesome-webfont.woff
[create] Public: fancybox\blank.gif
[create] Public: fancybox\fancybox_loading@2x.gif
[create] Public: fancybox\fancybox_overlay.png
[create] Public: css\images\banner.jpg
[create] Public: fancybox\fancybox_sprite.png
[create] Public: fancybox\jquery.fancybox.css
[create] Public: fancybox\fancybox_loading.gif
[create] Public: fancybox\fancybox_sprite@2x.png
[create] Public: fancybox\jquery.fancybox.js
[create] Public: fancybox\jquery.fancybox.pack.js
[create] Public: fancybox\helpers\jquery.fancybox-buttons.js
[create] Public: fancybox\helpers\fancybox_buttons.png
[create] Public: fancybox\helpers\jquery.fancybox-buttons.css
[create] Public: fancybox\helpers\jquery.fancybox-media.js
[create] Public: fancybox\helpers\jquery.fancybox-thumbs.css
[create] Public: fancybox\helpers\jquery.fancybox-thumbs.js
[create] Public: archives\index.html
[create] Public: images\fens.me.png
[create] Public: archives\2014\index.html
[create] Public: archives\2014\05\index.html
[create] Public: css\style.css
[create] Public: index.html
[create] Public: categories\日志\index.html
[create] Public: categories\日志\第一天\index.html
[create] Public: 2014\05\07\abc\index.html
[create] Public: 2014\05\07\hello-world\index.html
[create] Public: tags\开始\index.html
[create] Public: tags\我\index.html
[create] Public: tags\日记\index.html
[info] 35 files generated in 0.711s

在本地目录下,会生成一个public的目录,里面包括了所有静态化的文件。

4.2 发布到github

接下来,我们把这个博客发布到github。

在github中创建一个项目nodejs-hexo,项目地址:https://github.com/bsspirit/nodejs-hexo

编辑全局配置文件:_config.yml,找到deploy的部分,设置github的项目地址。


deploy:
  type: github
  repo: git@github.com:bsspirit/nodejs-hexo.git

然后,通过命令进行部署。


D:\workspace\javascript\nodejs-hexo>hexo deploy
[info] Start deploying: github
[info] Setting up GitHub deployment...
Initialized empty Git repository in D:/workspace/javascript/nodejs-hexo/.deploy/.git/
[master (root-commit) 43873d3] First commit
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 placeholder
[info] Clearing .deploy folder...
[info] Copying files from public folder...

// 省略部分输出

Branch gh-pages set up to track remote branch gh-pages from github.
To git@github.com:bsspirit/nodejs-hexo.git
 * [new branch]      gh-pages -> gh-pages
[info] Deploy done: github

这个静态的web网站就被部署到了github,检查一下分支是gh-pages。gh-pages是github为了web项目特别设置的分支。

hexo-github

然后,点击”Settings”,找到GitHub Pages,提示“Your site is published at http://bsspirit.github.io/nodejs-hexo”,打开网页 http://bsspirit.github.io/nodejs-hexo,就是我们刚刚发布的站点。

hexo-github-page

4.3 设置域名

看起来css和js的加载路径不太对,不过没有关系。接下来,我们配置好域名,这个路径就会正确的。比如,我有一个域名是 52u.me,为了中国DNS解析,我先把域名绑定在Dnspod管理,再做跳转。

域名有两种配置方式:

  • 主域名绑定:直接绑定主域名52u.me
  • 子域名绑定:绑定子域名blog.52u.me
    • 4.3.1 主域名绑定

      在dnspod控制台,设置主机记录@,类型A,到IP 192.30.252.153。

      dnspod-1

      大概等几分钟会生效。判断生效,对域名执行ping或者dig命令。

      
      D:\workspace\javascript\nodejs-hexo>ping 52u.me
      
      正在 Ping 52u.me [192.30.252.153] 具有 32 字节的数据:
      来自 192.30.252.153 的回复: 字节=32 时间=321ms TTL=48
      来自 192.30.252.153 的回复: 字节=32 时间=325ms TTL=48
      来自 192.30.252.153 的回复: 字节=32 时间=329ms TTL=48
      来自 192.30.252.153 的回复: 字节=32 时间=326ms TTL=48
      
      192.30.252.153 的 Ping 统计信息:
          数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),
      往返行程的估计时间(以毫秒为单位):
          最短 = 321ms,最长 = 329ms,平均 = 325ms
      

      在github项目中,新建一个文件CNAME,文件中写出你要绑定的域名52u.me。通过浏览器,访问http://52u.me,就打开了我们建好的博客站点。

      52u.me

      4.3.2 子域名绑定

      有时候,我们的主域名正在使用着,需要先新建一个博客绑定到子域名,比如: blog.52u.me。

      dnspod-2

      在dnspod控制台,我们要做3步设置。

      • 设置主机记录github,类型A,到IP 199.27.76.133
      • 设置主机记录bsspirit.github.io,类型CNAME,到github.52u.me.
      • 设置主机记录blog,类型CNAME,到 bsspirit.github.io

      记得我们还要修改文件CNAME,改为blog.52u.me。通过浏览器,访问http://blog.52u.me,就可以打开了我们的博客站点了,而这次用的是二级域名。

      由于每次执行deploy的时候,gh-pages分支所有的文件都会被覆盖,所以我们最好在source目录下创建这个CNAME文件,这样每次部署就不用动手创建了。

      5. 替换皮肤

      博客系统流行的原因,是因为他的个人性,而皮肤正式个性化的一种体现。

      利用hexo替换皮肤,还是比较简单的,3步完成。

      5.1 找到一个皮肤或者自己开发一个皮肤

      打开hexo的皮肤列表页面,你可以找到很多的皮肤,网页地址: https://github.com/tommy351/hexo/wiki/Themes。

      5.2. 放到themes目录下

      比如,我觉得pacman(https://github.com/A-limon/pacman)这个皮肤还不错,我就可以下载皮肤到themes目录下面。

      通过git命令下载皮肤

      
      git clone https://github.com/A-limon/pacman.git themes/pacman
      

      5.3. 在_config.yml指定皮肤

      编辑文件_config.yml,找到theme一行,改成 theme: pacman

      本地启动hexo服务器,打开浏览器 http://localhost:4000

      hexo-theme

      新皮肤的效果还不错吧,然后静态化处理,再发布到github,就完成了站点的改版。

      6. 配置常用插件

      6.1 Disqus评论系统

      首先登陆http://disqus.com/ 网站,申请一个新网站的shortname,配置到_config.yml文件里,disqus_shortname: blog52ume

      disqus

      然后,你会得到一段js代码,把他复制文件 themes/pacman/layout/_partial/comment.ejs 。继续修改themes/pacman/layout/layout.ejs文件,增加对comment.ejs的引用。具体修改请详见代码。

      disqus-comment

      这样,评论系统就增加好了!!

      6.2 RSS订阅

      这个功能非常简单,因为已经有人写好了插件,我们只要安装插件就行了。

      
      D:\workspace\javascript\nodejs-hexo>npm install hexo-generator-feed
      npm WARN package.json hexo-site@2.5.5 No readme data!
      npm http GET https://registry.npmjs.org/hexo-generator-feed
      npm http 304 https://registry.npmjs.org/hexo-generator-feed
      npm http GET https://registry.npmjs.org/ejs/0.8.5
      npm http GET https://registry.npmjs.org/lodash/2.4.1
      npm http 304 https://registry.npmjs.org/ejs/0.8.5
      npm http 200 https://registry.npmjs.org/lodash/2.4.1
      npm http GET https://registry.npmjs.org/lodash/-/lodash-2.4.1.tgz
      npm http 200 https://registry.npmjs.org/lodash/-/lodash-2.4.1.tgz
      hexo-generator-feed@0.1.0 node_modules\hexo-generator-feed
      ├── ejs@0.8.5
      └── lodash@2.4.1
      

      启动服务器,用浏览器打开 http://localhost:4000/atom.xml, 就可以看到RSS已经生效了。

      6.3 Sitemap站长地图

      同样是一条命令,就可以完成。

      
      D:\workspace\javascript\nodejs-hexo>npm install hexo-generator-sitemap
      npm WARN package.json hexo-site@2.5.5 No readme data!
      npm http GET https://registry.npmjs.org/hexo-generator-sitemap
      npm http 304 https://registry.npmjs.org/hexo-generator-sitemap
      npm http GET https://registry.npmjs.org/ejs/0.8.5
      npm http GET https://registry.npmjs.org/lodash/2.4.1
      npm http 304 https://registry.npmjs.org/lodash/2.4.1
      npm http 304 https://registry.npmjs.org/ejs/0.8.5
      hexo-generator-sitemap@0.1.1 node_modules\hexo-generator-sitemap
      ├── ejs@0.8.5
      └── lodash@2.4.1
      

      启动服务器,用浏览器打开 http://localhost:4000/sitemap.xml, 就可以看到sitemap已经生效了。

      6.4 mathjax数学公式

      有时候,我们还需要一些高级功能,比如在网页上显示数学公式。

      新建一个文件themes/pacman/layout/_partial/mathjax.ejs,找到mathjax的调用代码复制到文件。

      
      <!-- mathjax config similar to math.stackexchange -->
      
      <script type="text/x-mathjax-config">
        MathJax.Hub.Config({
          tex2jax: {
            inlineMath: [ ['$','$'], ["\\(","\\)"] ],
            processEscapes: true
          }
        });
      </script>
      
      <script type="text/x-mathjax-config">
          MathJax.Hub.Config({
            tex2jax: {
              skipTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code']
            }
          });
      </script>
      
      <script type="text/x-mathjax-config">
          MathJax.Hub.Queue(function() {
              var all = MathJax.Hub.getAllJax(), i;
              for(i=0; i < all.length; i += 1) {
                  all[i].SourceElement().parentNode.className += ' has-jax';
              }
          });
      </script>
      
      <script type="text/javascript" src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML">
      </script>
      

      在themes/pacman/layout/_partial/after_footer.ejs 的最后一行,增加对mathjax的引用,详细内容请查看源代码。

      我们修改文章:source/_posts/新的开始.md

      增加公式:

      
      ## 公式
      $$J\_\alpha(x)=\sum _{m=0}^\infty \frac{(-1)^ m}{m! \, \Gamma (m + \alpha + 1)}{\left({\frac{x}{2}}\right)}^{2 m + \alpha }$$
      

      启动服务器,查看效果:

      math

      当然,除了这些还有很多需要的功能,像Google分析,百度统计,微薄转发等,大家可以自己找找,也可以自己开发一些插件!

      最后,本文的中代码已经上传的github,https://github.com/bsspirit/nodejs-hexo。 其中master分支是项目源代码,gh-pages分支是发布的站点。

      Hexo框架确实如同它介绍中的话: “A fast, simple & powerful blog framework, powered by Node.js.”,Noder还等什么,赶紧搭建一个博客吧!!

      转载请注明出处:
      http://blog.fens.me/hexo-bootstarp-github/

      打赏作者

Nginx反向代理Websocket

从零开始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-websocket-nginx/

nodejs-nginx-ws

前言

用Nginx给网站做反向代理和负载均衡,是广泛使用的一种Web技术,不仅能够保证后端服务器的隐蔽性,还可以提高网站情况,部署灵活,而且以来源软件实现负载均衡性价比非常高。

不过今天要讲一下,如何用Nginx给Websocket服务器实现反向代理和负载均衡。我也是第一次做这样的尝试,所以仅把功能实现,优化以后再说。

目录

  1. 反向代理和负载均衡
  2. 创建基于Node的websocket服务器端
  3. 创建websocket客户端
  4. 用Nginx实现反向代理
  5. 用Nginx实现多websocket服务器的负载均衡

1. 反向代理和负载均衡

反向代理(Reverse Proxy)方式是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个服务器。

负载均衡 (Load Balancing) 负载均衡建立在现有网络结构之上,它提供了一种廉价有效透明的方法扩展网络设备和服务器的带宽、增加吞吐量、加强网络数据处理能力、提高网络的灵活性和可用性。

2. 创建基于Node的websocket服务器端

系统环境:

  • win7 64bit
  • npm 1.2.19
  • node v0.10.5
  • bower 1.1.2
  • nginx 1.5.11
  • redis 2.4.6

创建基于Node的websocket服务器端

ws1

新建工程


D:\workspace\javascript>mkdir nginx-websocket && cd nginx-websocket
D:\workspace\javascript\nginx-websocket>npm install socket.io
socket.io@0.9.16 node_modules\socket.io
├── base64id@0.1.0
├── policyfile@0.0.4
├── redis@0.7.3
└── socket.io-client@0.9.16 (xmlhttprequest@1.4.2, uglify-js@1.2.5, active-x-obfuscator@0.0.1, ws@0.4.31)

服务器端启动文件app1.js


~ vi app1.js

var port = 3100;
var io = require('socket.io').listen(port);

io.sockets.on('connection', function (socket) {
    socket.emit('server', {
        server: 'Server1',
        port: port
    });
});

启动程序

node app1.js

3. 创建websocket客户端

通过bower安装socket.io-client库和jquery库


#下载socket.io-client
D:\workspace\javascript\nginx-websocket>bower install socket.io-client
bower socket.io-client#*        cached git://github.com/LearnBoost/socket.io-client.git#0.9.16
bower socket.io-client#*      validate 0.9.16 against git://github.com/LearnBoost/socket.io-client.git#*
bower socket.io-client#~0.9.16 install socket.io-client#0.9.16

socket.io-client#0.9.16 bower_components\socket.io-client

#下载jquery
D:\workspace\javascript\nginx-websocket>bower install jquery
bower jquery#*                  cached git://github.com/jquery/jquery.git#2.1.0
bower jquery#*                validate 2.1.0 against git://github.com/jquery/jquery.git#*
bower jquery#*                     new version for git://github.com/jquery/jquery.git#*
bower jquery#*                 resolve git://github.com/jquery/jquery.git#*
bower jquery#*                download https://github.com/jquery/jquery/archive/2.1.0.tar.gz
bower jquery#*                progress received 0.3MB of 0.7MB downloaded, 46%
bower jquery#*                progress received 0.3MB of 0.7MB downloaded, 50%
bower jquery#*                progress received 0.4MB of 0.7MB downloaded, 56%
bower jquery#*                progress received 0.4MB of 0.7MB downloaded, 61%
bower jquery#*                progress received 0.5MB of 0.7MB downloaded, 67%
bower jquery#*                progress received 0.5MB of 0.7MB downloaded, 72%
bower jquery#*                progress received 0.5MB of 0.7MB downloaded, 77%
bower jquery#*                progress received 0.6MB of 0.7MB downloaded, 81%
bower jquery#*                progress received 0.6MB of 0.7MB downloaded, 86%
bower jquery#*                progress received 0.6MB of 0.7MB downloaded, 91%
bower jquery#*                progress received 0.6MB of 0.7MB downloaded, 94%
bower jquery#*                progress received 0.7MB of 0.7MB downloaded, 99%
bower jquery#*                 extract archive.tar.gz
bower jquery#*                resolved git://github.com/jquery/jquery.git#2.1.0
bower jquery#~2.1.0            install jquery#2.1.0
jquery#2.1.0 bower_components\jquery

创建静态的html文件,作为websocket客户端:client.html


~ vi client.html

<!DOCTYPE html>
<html>
<head>
<title>Websocket Client</title>
<script src="bower_components/socket.io-client/dist/socket.io.min.js"></script>
<script src="bower_components/jquery/dist/jquery.min.js"></script>
</head>
<body>
<h1>Socket.IO Client</h1>
<div id="content">connecting....</div>

<script>
var socket = io.connect('http://localhost:3100');
socket.on('server', function (data) {
console.log(data);
$('#content').html(JSON.stringify(data));
});
</script>

</body>
</html>

用浏览器打开client.html,查看websocket通讯情况。

websocket1

服务器端和客户端通讯成功。

接下来,我们用Nginx实现Websocket服务器的反向代理。

4. 用Nginx实现反向代理

看文官说明Nginx在1.3以后的版本支持websocket反向代理,但我在Nginx1.4版本测试时失败,nginx1.5.11版本测试成功。

ws2

操作步骤:

  • 1). 下载安装Nginx
  • 2). 修改Nginx配置文件,配置反向代理
  • 3). 启动Nginx服务器
  • 4). 修改app1.js,增加超时的设置
  • 5). 修改client.html,修改nginx访问端口
  • 6). 启动Node的Websocket服务器
  • 7). 浏览器访问测试

1). 下载安装Nginx

下载地址:http://nginx.org/en/download.html

2). 修改Nginx配置文件,配置反向代理

修改nginx配置文件:nginx.conf


http {

    // ...省略

    map $http_upgrade $connection_upgrade {
        default upgrade;
        ''      close;
    }

    server {

	    listen       3102;  #监听3102 
	    server_name localhost;

        location / {
            proxy_pass http://localhost:3100; #代理3100 

            proxy_set_header X-Real-IP $remote_addr;
	    proxy_set_header Host $host;
	    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

	    proxy_http_version 1.1;
	    proxy_set_header Upgrade $http_upgrade;
	    proxy_set_header Connection "upgrade";
        }
    }

    // ...省略
}
  • 设置nginx的访问端口:3102
  • 设置nginx的代理端口:3100

3). 启动Nginx服务器


D:\toolkit\nginx-1.5.11>nginx.exe

4). 修改app1.js,增加超时的设置

修改启动文件:app1.js


var port = 3100;
var io = require('socket.io').listen(port);

io.enable('browser client minification');  // send minified client
io.enable('browser client etag');          // apply etag caching logic based on version number
io.enable('browser client gzip');          // gzip the file
//io.set('log level', 1);                    // reduce logging

io.set('transports', [ 'websocket','flashsocket','htmlfile','xhr-polling','jsonp-polling']);

io.sockets.on('connection', function (socket) {

    socket.emit('server', {
        server: 'Server1',
        port: port
    });
});

5). 修改client.html,修改nginx访问端口


<!DOCTYPE html>
<html>
<head>
<title>Websocket Client</title>
<script src="bower_components/socket.io-client/dist/socket.io.min.js"></script>
<script src="bower_components/jquery/dist/jquery.min.js"></script>
</head>
<body>
<h1>Socket.IO Client</h1>
<div id="content">connecting....</div>

<script>
    var socket = io.connect('http://localhost:3102',{ // 访问3102端口
        'connect timeout': 500,
        'reconnect': true,
        'reconnection delay': 500,
        'reopen delay': 500,
        'max reconnection attempts': 10
    });//设置连接超时

    socket.on('server', function (data) {
        console.log(data);
        $('#content').html(JSON.stringify(data));
    });
</script>

</body>
</html>

6). 启动Node的Websocket服务器


D:\workspace\javascript\nginx-websocket>node app1.js

7). 浏览器访问测试

打开网页:

websocket2

这样就打通了nginx反向代理Websocket。不过遇到一个问题,就是tcp建立连接时特别慢,目前还没有好的解决办法。

5. 用Nginx实现多websocket服务器的负载均衡

接下来,我们在Nginx配置负载均衡。

ws3

由于基于Socket.io实现的Websocket服务器是单线程的,而访问实例被保存在MemoryStore中。如果我们想实现多个Websocket服务器或多进程调用,那么我们需要把访问实例单独存储,Socket.io集成了通过Redis的NoSQL数据库存储。

socket.io配置说明,可以参考:Configuring Socket.IO

操作步骤:

  • 1). 下载安装Redis服务器并启动
  • 2). 修改app1.js,增加Redis配置
  • 3). 新建app2.js,增加Redis配置
  • 4). 修改Nginx配置文件,配置app1,app2的负载均衡
  • 5). 启动Nginx服务器,启动app1.js, app2.js
  • 6). 浏览器访问测试

1). 下载安装Redis服务器并启动

Redis的window版本的下载地址:https://github.com/rgl/redis/downloads

启动redis


D:\toolkit\Redis>redis-server.exe

2). 修改app1.js,增加Redis配置

修改一个app1.js,绑定Redis存储实例


var port = 3100;
var io = require('socket.io').listen(port);

var RedisStore = require('socket.io/lib/stores/redis')
    , redis  = require('socket.io/node_modules/redis')
    , pub    = redis.createClient()
    , sub    = redis.createClient()
    , client = redis.createClient();

io.set('store', new RedisStore({
    redisPub : pub
    , redisSub : sub
    , redisClient : client
}));

io.enable('browser client minification');  // send minified client
io.enable('browser client etag');          // apply etag caching logic based on version number
io.enable('browser client gzip');          // gzip the file
//io.set('log level', 1);                    // reduce logging

io.set('transports', [ 'websocket','flashsocket','htmlfile','xhr-polling','jsonp-polling']);

io.sockets.on('connection', function (socket) {

    socket.emit('server', {
        server: 'Server1',
        port: port
    });
});

3). 新建app2.js,增加Redis配置

增加一个app2.js,做为第二个websocket服务器


var port = 3101;
var io = require('socket.io').listen(port);

var RedisStore = require('socket.io/lib/stores/redis')
    , redis  = require('socket.io/node_modules/redis')
    , pub    = redis.createClient()
    , sub    = redis.createClient()
    , client = redis.createClient();

io.set('store', new RedisStore({
    redisPub : pub
    , redisSub : sub
    , redisClient : client
}));

io.enable('browser client minification');  // send minified client
io.enable('browser client etag');          // apply etag caching logic based on version number
io.enable('browser client gzip');          // gzip the file
//io.set('log level', 1);                    // reduce logging

io.set('transports', [ 'websocket','flashsocket','htmlfile','xhr-polling','jsonp-polling']);

io.sockets.on('connection', function (socket) {

    socket.emit('server', {
        server: 'Server2',
        port: port
    });
});

4). 修改Nginx配置文件,配置3100,3101端口的负载均衡

修改Nginx配置文件:nginx.conf


http{
	map $http_upgrade $connection_upgrade {
		default upgrade;
		''      close;
	}

	upstream websocket {
		#ip_hash;
		server localhost:3100;  
		server localhost:3101;
	}

	server {
		listen       3102;
		server_name localhost;

		location / {
			proxy_pass http://websocket;
			
			#proxy_pass http://localhost:3100/;  
			
			proxy_set_header X-Real-IP $remote_addr;
			proxy_set_header Host $host;
			proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

			proxy_http_version 1.1;
			proxy_set_header Upgrade $http_upgrade;
			proxy_set_header Connection "upgrade";
		}
	}
}

5). 启动Nginx服务器,启动app1.js, app2.js


D:\toolkit\nginx-1.5.11>nginx.exe
D:\workspace\javascript\nginx-websocket>node app1.js
D:\workspace\javascript\nginx-websocket>node app2.js

6). 浏览器访问测试

打开2个浏览器页面:

websocket3

这样就实现2个Websocket服务器轮训的负载均衡!!不过,建立连接非常慢的问题依然存在,我还没有什么好的办法。希望了解内情的同学,帮助解答!

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

打赏作者

Passport现实社交网络OAuth登陆

从零开始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-oauth-passport/

nodejs-passport-oauth

前言

随着社交网络的发展,开发一个应用门槛越来越低。从一个完整的应用系统,到一个部署在社交网络平台的APP;从数据库–》应用层–》展示层,变成只需要开发展示层。

很多的社交应用,甚至都放弃了用户注册!仅靠大型社交网站的登陆授权,就可以赚到100W以上的用户量。。。

减少用户管理代码开发及维护,更专注于应用本身,个人开发者已经崛起!!

目录

  1. Passport介绍
  2. OAuth介绍
  3. 登陆Github
  4. 登陆LinkedIn

1. Passport介绍

Passport项目,主要是为了解决登陆认证的问题。

Web应用一般有2种登陆认证的形式:

  • 用户名和密码认证登陆
  • OAuth认证登陆

在上一篇文章中,我们介绍了Passport的项目,通过用户名和密码认证登陆。Express结合Passport实现登陆认证

本文将介绍,通过Passport实现OAuth登陆认证。

2. OAuth介绍

OAuth协议为用户资源的授权提供了一个安全的、开放而又简易的标准。与以往的授权方式不同之处是OAuth的授权不会使第三方触及到用户的帐号信息(如用户名与密码),即第三方无需使用用户的用户名与密码就可以申请获得该用户资源的授权,因此OAuth是安全的。

  • 简单:不管是OAUTH服务提供者还是应用开发者,都很易于理解与使用
  • 安全:没有涉及到用户密钥等信息,更安全更灵活
  • 开放:任何服务提供商都可以实现OAUTH,任何软件开发商都可以使用OAUTH

OAuth认证授权就三个步骤,三句话可以概括:

  • 获取未授权的Request Token
  • 获取用户授权的Request Token
  • 用授权的Request Token换取Access Token

OAuth的介绍,摘自:http://baike.baidu.com/view/3948029.htm

3. Github登陆

通过Passport实现Github登陆。我们还使用上一篇文章中的环境:Express结合Passport实现登陆认证

  • 申请开发github应用
  • Passport程序实现
  • 运行Github登陆认证

1). 申请开发github应用

github-app

2). Passport程序实现

  • 安装Passport的github扩展
  • 增加Github认证策略
  • 定义Github认证的路由配置: 登陆,回调,展示

a. 安装Passport的github扩展


D:\workspace\javascript\nodejs-passport>npm install passport-github

b. 增加Github认证策略

修改app.js


passport.use(new GithubStrategy({//对应从Github申请KEY
    clientID: "XXXX",
    clientSecret: "XXXX",
    callbackURL: "http://localhost:3000/auth/github/callback"
},function(accessToken, refreshToken, profile, done) {
    done(null, profile);
}));

c. 定义Github认证的路由配置: 登陆,回调,展示

  • /auth/github: 通过github,登陆
  • /auth/github/callback: github认证成功后,回调
  • /github: 回调验证后,转向展示示

修改app.js


app.all('/github', isLoggedIn);
app.get("/github",user.github);

app.get("/auth/github", passport.authenticate("github",{ scope : "email"}));
app.get("/auth/github/callback",
    passport.authenticate("github",{
        successRedirect: '/github',
        failureRedirect: '/'
    }));

3).运行Github登陆认证

github-auth

程序日志


D:\workspace\javascript\nodejs-passport>node app.js
Express server listening on port 3000
GET / 200 401ms - 594b
GET /stylesheets/style.css 304 9ms
GET /auth/github 302 8ms - 424b
GET /auth/github/callback?code=7cf818c4590e2aacfe90 302 4052ms - 70b
GET /github 200 4ms - 139b
GET /logout 302 2ms - 58b
GET / 200 3ms - 594b
GET /stylesheets/style.css 304 2ms

4. LinkedIn登陆

  • 申请开发LinkedIn应用
  • Passport程序实现
  • 运行LinkedIn登陆认证

1). 申请开发LinkedIn应用

linkedin-app

2). Passport程序实现

  • 安装Passport的LinkedIn扩展
  • 增加LinkedIn认证策略
  • 定义LinkedIn认证的路由配置: 登陆,回调,展示

a. 安装Passport的LinkedIn扩展


D:\workspace\javascript\nodejs-passport>npm install passport-linkedin

b. 增加LinkedIn认证策略

修改app.js


passport.use(new LinkedinStrategy({
    consumerKey: "XXXX",
    consumerSecret: "XXXX",
    callbackURL: "http://localhost:3000/auth/linkedin/callback",
    userAgent: 'localhost'
},function(accessToken, refreshToken, profile, done) {
    done(null, profile);
}));

c. 定义LinkedIn认证的路由配置: 登陆,回调,展示

  • /auth/linkedin: 通过LinkedIn,登陆
  • /auth/linkedin/callback: github认证成功后,回调
  • /linkedin: 回调验证后,转向展示示

修改app.js


app.all('/github', isLoggedIn);
app.get("/github",user.github);

app.all('/linkedin', isLoggedIn);
app.get("/linkedin",user.linkedin);
app.get("/auth/linkedin", passport.authenticate("linkedin",{}));
app.get("/auth/linkedin/callback",
    passport.authenticate("linkedin",{
        successRedirect: '/linkedin',
        failureRedirect: '/'
    }));

3).运行LinkedIn登陆认证

linkedin-auth

程序日志


D:\workspace\javascript\nodejs-passport>node app.js
Express server listening on port 3000
GET / 200 399ms - 638b
GET /stylesheets/style.css 304 4ms
GET /auth/Linkedin 302 3092ms - 256b
GET /auth/linkedin/callback?oauth_token=75--8f032180-afae-489b-bc3c-3326d80bea6f&oauth_verifier=36091 302 3049ms - 74b
GET /linkedin 200 5ms - 76b
GET /logout 302 1ms - 58b
GET / 200 19ms - 638b
GET /stylesheets/style.css 304 2ms

5. 完整的应用

项目代码我已上传到了github, 项目地址:https://github.com/bsspirit/nodejs-passport

下载及安装


git clone https://github.com/bsspirit/nodejs-passport
npm install

我们非常方便地,实现了Github和LinkedIn登陆认证,是否已经体会到Passport的强大了!把权限这种基础功能,进行合理的封装,是会帮我们节省大量的工作量的。

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

打赏作者

Express结合Passport实现登陆认证

从零开始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-express-passport/

nodejs-passport

前言

登陆认证,是每个应用都需要的基础功能。但很多的时候,却都被大家所忽略,不仅安全漏洞严重,而且代码紧耦合,混乱不堪。

Passport项目,正是为了解决登陆认证的事情,让认证模块更透明,减少耦合!

目录

  1. 什么是认证(Authentication)?
  2. Passport项目介绍
  3. Express结合Passport实现登陆认证

1. 什么是登陆认证(Authentication)?

认证又称“验证”、“鉴权”,是指通过一定的手段,完成对用户身份的确认。身份验证的方法有很多,基本上可分为:基于共享密钥的身份验证、基于生物学特征的身份验证和基于公开密钥加密算法的身份验证。

登陆认证,是用户在访问应用或者网站时,通过是先注册的用户名和密码,告诉应用使用者的身份,从而获得访问权限的一种操作。

几乎所有的应用都需要登陆认证!

2. Passport项目介绍

Passport项目是一个基于Nodejs的认证中间件。Passport目的只是为了“登陆认证”,因此,代码干净,易维护,可以方便地集成到其他的应用中。

Web应用一般有2种登陆认证的形式:

  • 用户名和密码认证登陆
  • OAuth认证登陆

Passport可以根据应用程序的特点,配置不同的认证机制。本文将介绍,用户名和密码的认证登陆。

项目网站:http://passportjs.org/

3. Express结合Passport实现登陆认证

系统环境:

  1. Win7 64bit 旗舰版
  2. node v0.10.5
  3. npm 1.2.19

1). 新建项目


D:\workspace\javascript>express -e nodejs-passport
D:\workspace\javascript>cd nodejs-passport && npm install
D:\workspace\javascript\nodejs-passport>npm install passport
D:\workspace\javascript\nodejs-passport>npm install passport-local

2). 实现Session的认证:

  • 启用connet的session中间件
  • connet的session中间件,同时依赖于connect的cookieParser中间件
  • 配置passport中间件

关于connect框架的详细说明,请参考文章:Nodejs基础中间件Connect

修改app.js


app.use(express.cookieParser())
app.use(express.session({secret: 'blog.fens.me', cookie: { maxAge: 60000 }}));
app.use(passport.initialize());
app.use(passport.session());

3). 定义认证策略:

LocalStrategy策略,用于匹配本地环境的用户名和密码,可以扩展这个策略,通过数据库的方式进行匹配。

修改app.js


var passport = require('passport')
    , LocalStrategy = require('passport-local').Strategy;

passport.use('local', new LocalStrategy(
    function (username, password, done) {
        var user = {
            id: '1',
            username: 'admin',
            password: 'pass'
        }; // 可以配置通过数据库方式读取登陆账号

        if (username !== user.username) {
            return done(null, false, { message: 'Incorrect username.' });
        }
        if (password !== user.password) {
            return done(null, false, { message: 'Incorrect password.' });
        }

        return done(null, user);
    }
));

passport.serializeUser(function (user, done) {//保存user对象
    done(null, user);//可以通过数据库方式操作
});

passport.deserializeUser(function (user, done) {//删除user对象
    done(null, user);//可以通过数据库方式操作
});

4). 路由控制和登陆认证

路由页面

  • /: 首页,用于登陆,未登陆用户只能访问首页
  • /login: 登陆请求,用户登陆时,POST到登陆请求,认证成功跳到用户页,认证失败回到首页
  • /users: 用户页,用户通过登陆认证后,可以访问用户页
  • /logout: 登出请求,用户退出系统,GET到登出请求,页面自动跳回首页

修改app.js


app.get('/', routes.index);
app.post('/login',
    passport.authenticate('local', {
        successRedirect: '/users',
        failureRedirect: '/'
    }));

app.all('/users', isLoggedIn);
app.get('/users', user.list);
app.get('/logout', function (req, res) {
    req.logout();
    res.redirect('/');
});

function isLoggedIn(req, res, next) {
    if (req.isAuthenticated())
        return next();

    res.redirect('/');
}

5). 运行程序

passport-login

运行日志


D:\workspace\javascript\nodejs-passport>node app.js
Express server listening on port 3000
POST /login 302 389ms - 68b
GET /users 200 2ms - 50b
GET /logout 302 2ms - 58b
GET / 200 7ms - 540b
GET /stylesheets/style.css 304 6ms
POST /login 302 2ms - 58b
GET / 200 2ms - 540b
GET /stylesheets/style.css 304 2ms

6). 完整的代码

  • app.js代码
  • index.ejs代码
  • user.js代码

app.js代码


var express = require('express')
    , routes = require('./routes')
    , user = require('./routes/user')
    , http = require('http')
    , path = require('path')
    , app = express();

var passport = require('passport')
    , LocalStrategy = require('passport-local').Strategy;

app.set('port', process.env.PORT || 3000);
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.cookieParser())
app.use(express.session({secret: 'blog.fens.me', cookie: { maxAge: 60000 }}));
app.use(passport.initialize());
app.use(passport.session());
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));

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

passport.use('local', new LocalStrategy(
    function (username, password, done) {
        var user = {
            id: '1',
            username: 'admin',
            password: 'pass'
        }; // 可以配置通过数据库方式读取登陆账号

        if (username !== user.username) {
            return done(null, false, { message: 'Incorrect username.' });
        }
        if (password !== user.password) {
            return done(null, false, { message: 'Incorrect password.' });
        }

        return done(null, user);
    }
));

passport.serializeUser(function (user, done) {//保存user对象
    done(null, user);//可以通过数据库方式操作
});

passport.deserializeUser(function (user, done) {//删除user对象
    done(null, user);//可以通过数据库方式操作
});

app.get('/', routes.index);
app.post('/login',
    passport.authenticate('local', {
        successRedirect: '/users',
        failureRedirect: '/'
    }));

app.all('/users', isLoggedIn);
app.get('/users', user.list);
app.get('/logout', function (req, res) {
    req.logout();
    res.redirect('/');
});

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

function isLoggedIn(req, res, next) {
    if (req.isAuthenticated())
        return next();

    res.redirect('/');
}

index.ejs代码


<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body>
<h1>Login</h1>
<form action="/login" method="post">
<div>
<label>Username:</label>
<input type="text" name="username"/>
</div>
<div>
<label>Password:</label>
<input type="password" name="password"/>
</div>
<div>
<input type="submit" value="Log In"/>
</div>
</form>
</body>
</html>

user.js代码


exports.list = function (req, res) {
var html = "<h2>你好, " + req.user.username + "</h2><a href='/logout'>退出</a>";
res.send(html);
};

通过Passport中间件,我们就把登陆认证和应用程序分离了出来,从而保证了更清晰代码结构。当然,我们也可不用Passport,在Express中直接实现登陆认证,可以参考文章:Nodejs开发框架Express3.0开发手记–从零开始

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

打赏作者

restify构建REST服务

从零开始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-restify/

nodejs-restify

前言

随着互联网应用的兴起,web2.0时代的到来,越来越多的人,选择用REST编程来代替原来的页面渲染。REST以资源为中心的web服务,分离了展现层和服务层,让前端和后端程序员能更专注于自己擅长的领域。

restify让REST变得如此简单!

目录

  1. 什么是REST?
  2. restify介绍
  3. restify安装
  4. restify服务端API
  5. restify客户端API

1. 什么是REST?

REST(Representational State Transfer表述性状态转移)是一种针对网络应用的设计和开发方式,可以降低开发的复杂性,提高系统的可伸缩性。

REST 定义了一组体系架构原则,您可以根据这些原则设计以系统资源为中心的 Web 服务,包括使用不同语言编写的客户端如何通过 HTTP 处理和传输资源状态。 如果考虑使用它的 Web 服务的数量,REST 近年来已经成为最主要的 Web 服务设计模式。 事实上,REST 对 Web 的影响非常大,由于其使用相当方便,已经普遍地取代了基于 SOAP 和 WSDL 的接口设计。

REST中的资源所指的不是数据,而是数据和表现形式的组合,比如“最新访问的10位会员”和“最活跃的10位会员”在数据上可能有重叠或者完全相同,而由于他们的表现形式不同,所以被归为不同的资源,这也就是为什么REST的全名是Representational State Transfer的原因。资源标识符就是URI(Uniform Resource Identifier),不管是图片,Word还是视频文件,甚至只是一种虚拟的服务,也不管你是xml格式、txt文件格式还是其它文件格式,全部通过 URI对资源进行唯一的标识。

文字介绍,摘自: http://baike.baidu.com/view/1077487.htm

2. restify介绍

restify是一个基于Nodejs的REST应用框架,支持服务器端和客户端。restify比起express更专注于REST服务,去掉了express中的template, render等功能,同时强化了REST协议使用,版本化支持,HTTP的异常处理。

restify提供了DTrace功能,为程序调式带来新的便利!

restifty的发布页:http://mcavage.me/node-restify/

3. restify安装

系统环境

  • Linux: Ubuntu 12.04 LTS 64bit
  • node: v0.6.12
  • npm: 1.1.4

创建项目


~ cd /home/conan/nodejs
~ mkdir nodejs-restify && cd nodejs-restify

~ sudo npm install restify
restify@2.6.1 node_modules/restify
├── assert-plus@0.1.4
├── once@1.3.0
├── deep-equal@0.0.0
├── escape-regexp-component@1.0.2
├── qs@0.6.5
├── tunnel-agent@0.3.0
├── keep-alive-agent@0.0.1
├── lru-cache@2.3.1
├── node-uuid@1.4.0
├── negotiator@0.3.0
├── mime@1.2.11
├── semver@2.2.1
├── spdy@1.14.12
├── backoff@2.3.0
├── formidable@1.0.14
├── csv@0.3.6
├── dtrace-provider@0.2.8
├── verror@1.3.6 (extsprintf@1.0.2)
├── bunyan@0.22.0 (mv@0.0.5)
└── http-signature@0.10.0 (assert-plus@0.1.2, asn1@0.1.11,ctype@0.5.2)

创建一个简单的rest服务

新建文件:app.js


~ vi app.js

var restify = require('restify');

function respond(req, res, next) {
  res.send('hello ' + req.params.name);
}

var server = restify.createServer();
server.get('/hello/:name', respond);
server.head('/hello/:name', respond);

server.listen(3900, function() {
  console.log('%s listening at %s', server.name, server.url);
});

运行程序


~ node app.js
restify listening at http://0.0.0.0:3900

使用curl访问测试


#空路径
~ curl localhost:3900
{"code":"ResourceNotFound","message":"/ does not exist"

#正常GET请求
~ curl localhost:3900/hello/conan
"hello conan"

#设置content-type, 返回包括header信息
~ curl -i localhost:3900/hello/conan -H 'accept:text/plain'
HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 11
Date: Mon, 13 Jan 2014 08:31:30 GMT
Connection: keep-alive

hello conan

#设置connection:close, 返回包括header信息
~  curl -i localhost:3900/hello/conan -H 'connection:close'
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 13
Date: Mon, 13 Jan 2014 08:29:38 GMT
Connection: close

hello conan

我们看到用restify创建REST服务非常的容易,接下来我们将深入学习restify的API。

4. restify服务端API

  • restify.createServer(): 创建服务器
  • server.use(): 注册handler
  • server.get(): 路由控制
  • server.formatters: 设置content-type
  • 异常处理
  • socket.IO集成/li>
  • Server API
  • 组件管理
  • Request API
  • Response API

1). restify.createServer(): 创建服务器

创建服务器代码


var restify = require('restify');

var server = restify.createServer({
  certificate: ...,
  key: ...,
  name: 'MyApp',
});

server.listen(8080);

服务器创建参数

  • certificate: string, HTTPS服务器的证书
  • key: string, HTTPS服务器的证书key
  • formatters: object, 自定义的response的content-type
  • log: object, 服务器日志,可以配合bunyan一起使用
  • name: string, 服务器的response header
  • spdy: Object, 允许集成node-spdy服务器
  • version: string, 路由版本
  • responseTimeHeader: string, X-Response-Time
  • responseTimeFormatter: function, 格式化header的值

2). server.use(): 注册handler

注册服务器控制组件,按照代码顺序执行,需要放在路由代码之前。

3). server.get(): 路由控制

REST响应G请求:

  • server.get(): 响应GET请求
  • server.post(): 响应POST请求
  • server.put(): 响应PUT请求
  • server.head(): 响应HEAD请求
  • server.del(): 响应DELETE请求

路由控制代码


 function send(req, res, next) {
   res.send('hello ' + req.params.name);
   return next();
 }

 server.post('/hello', function create(req, res, next) {
   res.send(201, Math.random().toString(36).substr(3, 8));
   return next();
 });
 server.put('/hello', send);
 server.get(/^\/([a-zA-Z0-9_\.~-]+)\/(.*)/, send);
 server.head('/hello/:name', send);
 server.del('hello/:name', function rm(req, res, next) {
   res.send(204);
   return next();
 });

4). server.formatters: 设置Response的content-type

content-type类型

  • application/json
  • text/plain
  • application/octet-stream

增加自定义的content-type:application/foo


var server = restify.createServer({
  formatters: {
    'application/foo': function formatFoo(req, res, body) {
      if (body instanceof Error)
        return body.stack;

      if (Buffer.isBuffer(body))
        return body.toString('base64');

      return util.inspect(body);
    }
  }
});

显式的设置Response的content-type


res.setHeader('content-type', 'application/foo');
res.send({hello: 'world'});

5). 异常处理

异常处理代码


var server = restify.createServer();
server.get('/hello/:name', function(req, res, next) {
  return next(new restify.InvalidArgumentError("I just don't like you"));
});

运行程序


~ curl -i localhost:3900/err/conan

HTTP/1.1 409 Conflict
Content-Type: application/json
Content-Length: 60
Date: Mon, 13 Jan 2014 11:06:26 GMT
Connection: keep-alive

RestError的内置的异常类型:

  • BadDigestError: 400
  • BadMethodError: 405
  • InternalError: 500
  • InvalidArgumentError: 409
  • InvalidContentError: 400
  • InvalidCredentialsError: 401
  • InvalidHeaderError: 400
  • InvalidVersionError: 400
  • MissingParameterError: 409
  • NotAuthorizedError: 403
  • RequestExpiredError: 400
  • RequestThrottledError: 429
  • ResourceNotFoundError: 404
  • WrongAcceptError: 406

自定义异常:MyError, errorCode:418


var restify = require('restify');
var util = require('util');

function MyError(message) {
  restify.RestError.call(this, {
    restCode: 'MyError'
    statusCode: 418,
    message: message,
    constructorOpt: MyError
  });
  this.name = 'MyError';
};
util.inherits(MyError, restify.RestError);

6). Socket.IO集成

服务器端集成代码


var server = restify.createServer();
var io = socketio.listen(server);

server.get('/', function indexHTML(req, res, next) {
    fs.readFile(__dirname + '/index.html', function (err, data) {
        if (err) {
            next(err);
            return;
        }

        res.setHeader('Content-Type', 'text/html');
        res.writeHead(200);
        res.end(data);
        next();
});


io.sockets.on('connection', function (socket) {
    socket.emit('news', { hello: 'world' });
    socket.on('my other event', function (data) {
            console.log(data);
    });
});

server.listen(8080, function () {
    console.log('socket.io server listening at %s', server.url);
});

7). Server API

Events事件监听:

  • NotFound: 404 handler
  • MethodNotAllowed: 405 handler
  • VersionNotAllowed: 400 handler
  • UnsupportedMediaType: 415 handler
  • after: 在所有handler之后执行
  • uncaughtException: 未处理的异常

注: uncaughtException的处理,可以参考文章, Nodejs异步异常处理domain

Properties配置属性:

  • name: string, 服务器名字
  • version: string, 路由默认版本
  • log: Object, 日志对象
  • acceptable: Array(String), content-types列表
  • url: string, 服务器信息

Methods函数:

  • address(): 绑定地址
  • listen(port, [host], [callback]): 启动服务器
  • close(): 停止服务器
  • pre(): 在路由之前触发的组件
  • use(): 注册组件

8). 组件管理

restify已支持的组件

  • Accept header parsing: 解析aceept header,返回客户端
  • Authorization header parsing: HTTP Basic Auth认证
  • Date header parsing: 数据头解析
  • JSONP support: JSONP请求
  • Gzip Response: 设置accept-encoding:gzip
  • Query string parsing: 解析URL参数
  • Body parsing (JSON/URL-encoded/multipart form): 解析内容
  • Static file serving: 静态文件处理
  • Throttling: 优化服务器性能配置
  • Conditional request handling: 设置请求条件
  • Audit logger: 日志记录

注册组件server.use()


var server = restify.createServer();
server.use(restify.acceptParser(server.acceptable));
server.use(restify.authorizationParser());
server.use(restify.dateParser());
server.use(restify.queryParser());
server.use(restify.jsonp());
server.use(restify.gzipResponse());
server.use(restify.bodyParser());
server.get(/\/docs\/public\/?.*/, restify.serveStatic({
  directory: './public'
}));
server.use(restify.throttle({
  burst: 100,
  rate: 50,
  ip: true,
  overrides: {
    '192.168.1.1': {
      rate: 0,        // unlimited
      burst: 0
    }
  }
}));
server.use(function setETag(req, res, next) {
  res.header('ETag', 'myETag');
  res.header('Last-Modified', new Date());
});
server.use(restify.conditionalRequest());
server.on('after', restify.auditLogger({
  log: bunyan.createLogger({
    name: 'audit',
    stream: process.stdout
  })
}));

9). Request API

对node内核API:http.ServerRequest的封装

10). Response API

对node内核API:http.ServerResponse的封装

5. restify客户端API

  • JsonClient: 收application/json, 发application/json
  • StringClient: 收text/plain, 发url-encoded request
  • HttpClient: 封装http/https

新建服务器端测试程序


var restify = require('restify');
var server = restify.createServer();
server.use(restify.acceptParser(server.acceptable));
server.use(restify.authorizationParser());
server.use(restify.dateParser());
server.use(restify.queryParser());
server.use(restify.jsonp());
server.use(restify.gzipResponse());
server.use(restify.bodyParser());

# 用于处理JsonClient请求
server.get('/json/v1',function(req,res,next){
  var a = {name:'conan',blog:'blog.fens.me'}
  res.send(a);
});

# 用于处理StringClient请求
server.get('/json/v2',function(req,res,next){
  var a = {name:'conan',blog:'blog.fens.me'}
  res.send(JSON.stringify(a));
});


server.listen(3900, function() {
  console.log('%s listening at %s', server.name, server.url);
});

1). JsonClient

curl请求


~ curl -i http://localhost:3900/json/v1

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 38
Date: Mon, 13 Jan 2014 12:09:36 GMT
Connection: keep-alive

{"name":"conan","blog":"blog.fens.me"}

创建文件:jsonclient.js


~ vi jsonclient.js

var restify = require('restify');

var client = restify.createJsonClient({
  url: 'http://localhost:3900'
});

client.get('/json/v1', function(err, req, res, obj) {
  if(err) console.log(err)
  console.log(JSON.stringify(obj, null, 2));
});

运行程序:


~ node jsonclient.js
{
  "name": "conan",
  "blog": "blog.fens.me"
}

2). StringClient

curl请求


~ curl -i http://localhost:3900/json/v2 -H 'accept:text/plain'

HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 38
Date: Mon, 13 Jan 2014 12:10:07 GMT
Connection: keep-alive

{"name":"conan","blog":"blog.fens.me"}

创建文件:stringclient.js


~ vi stringclient.js

var restify = require('restify');

var client = restify.createStringClient({
  url: 'http://localhost:3900'
});

client.get('/json/v2', function(err, req, res, obj) {
  if(err) console.log(err)
  console.log(JSON.stringify(obj, null, 2));
});

运行程序:


~ node stringclient.js
"{\"name\":\"conan\",\"blog\":\"blog.fens.me\"}"

3). HttpClient

创建文件: httpclient.js


~ vi httpclient.js

var restify = require('restify');

var client = restify.createClient({
  url: 'http://localhost:3900'
});

client.get('/json/v1', function(err, req) {

  req.on('result', function(err, res) {
    res.body = '';
    res.setEncoding('utf8');
    res.on('data', function(chunk) {
      res.body += chunk;
    });

    res.on('end', function() {
      console.log(res.body);
    });
});
});

运行程序


~ node httpclient.js
{"name":"conan","blog":"blog.fens.me"}

我们已经全面了解的了restify包,接下就可以快速构建我们自己的REST服务了。Node实现如此之简单,让原来Java程序员如何生存呢?!

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

打赏作者