<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>embbnux (Embbnux Ji)</title>
    <link>https://ruby-china.org/embbnux</link>
    <description>静下心来学习</description>
    <language>en-us</language>
    <item>
      <title>[Kails] 一个基于 Koa2 构建的类似于 Rails 的 nodejs 开源项目</title>
      <description>&lt;p&gt;最近研究了下 Koa2 框架，喜爱其中间件的思想。但是发现实在是太简洁了，只有基本功能，虽然可以方便搭各种服务，但是离可以适应快速开发的网站框架还是有点距离。于是参考 Rails 的大致框架搭建了个网站框架 kails, 配合 postgres 和 redis, 实现了 MVC 架构，前端 webpack，react 前后端同构等网站开发基本框架。本文主要介绍 kails 搭建中的各种技术栈和思想。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;本文首发于&lt;a href="https://www.embbnux.com" rel="nofollow" target="_blank" title=""&gt;Blog of Embbnux&lt;/a&gt;, 转载请注明原文出处:
&lt;a href="https://www.embbnux.com/2016/09/04/kails_with_koa2_like_ruby_on_rails/" rel="nofollow" target="_blank" title=""&gt;https://www.embbnux.com/2016/09/04/kails_with_koa2_like_ruby_on_rails/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;koa 来源于 express 的主创团队，主要利用 es6 的 generators 特性实现了基于中间件思想的新的框架，但是和 express 不同，koa 并不想 express 一样提供一个可以满足基本网站开发的框架，而更像是一个基本功能模块，要满足网站还是需要自己引入很多功能模块。所以根据选型大的不同，有各种迥异的 koa 项目，kails 由名字也可以看出是一个类似 Ruby on Rails 的 koa 项目。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;项目地址：  &lt;a href="https://github.com/embbnux/kails" rel="nofollow" target="_blank" title=""&gt;https://github.com/embbnux/kails&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="主要目录结构如下:"&gt;主要目录结构如下：&lt;/h2&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;├── app.js
├── assets
│&amp;nbsp;&amp;nbsp; ├── images
│&amp;nbsp;&amp;nbsp; ├── javascripts
│&amp;nbsp;&amp;nbsp; └── stylesheets
├── config
│&amp;nbsp;&amp;nbsp; ├── config.js
│   ├── development.js
│   ├── test.js
│   ├── production.js
│&amp;nbsp;&amp;nbsp; └── webpack.config.js
│   ├── webpack
├── routes
├── models
├── controllers
├── views
├── db
│&amp;nbsp;&amp;nbsp; └── migrations
├── helpers
├── index.js
├── package.json
├── public
└── test
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="一、第一步es6支持"&gt;一、第一步 es6 支持&lt;/h2&gt;
&lt;p&gt;kails 选用的是 koa2 作为核心框架，koa2 使用 es7 的 async 和 await 等功能，node 在开启 harmony 后还是不能运行，所以要使用 babel 等语言转化工具进行支持:
babel6 配置文件:
.babelrc:&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "presets": [
    "es2015",
    "stage-0",
    "react"
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在入口使用 babel 加载整个功能，使支持 es6&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;require('babel-core/register')
require('babel-polyfill')
require('./app.js')
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="二、核心文件app.js"&gt;二、核心文件 app.js&lt;/h2&gt;
&lt;p&gt;app.js 是核心文件，koa2 的中间件的引入和使用主要在这里，这里会引入各种中间件和配置，具体详细功能介绍后面会慢慢涉及到。&lt;/p&gt;

&lt;p&gt;下面是部分内容，具体内容见 github 上仓库&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import Koa from 'koa'
import session from 'koa-generic-session'
import csrf from 'koa-csrf'
import views from 'koa-views'
import convert from 'koa-convert'
import json from 'koa-json'
import bodyParser from 'koa-bodyparser'

import config from './config/config'
import router from './routes/index'
import koaRedis from 'koa-redis'
import models from './models/index'

const redisStore = koaRedis({
  url: config.redisUrl
})

const app = new Koa()

app.keys = [config.secretKeyBase]

app.use(convert(session({
  store: redisStore,
  prefix: 'kails:sess:',
  key: 'kails.sid'
})))

app.use(bodyParser())
app.use(convert(json()))
app.use(convert(logger()))

// not serve static when deploy
if(config.serveStatic){
  app.use(convert(require('koa-static')(__dirname + '/public')))
}

//views with pug
app.use(views('./views', { extension: 'pug' }))

// csrf
app.use(convert(csrf()))

app.use(router.routes(), router.allowedMethods())

app.listen(config.port)
export default app

&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="三、MVC框架搭建"&gt;三、MVC 框架搭建&lt;/h2&gt;
&lt;p&gt;网站架构还是以 mvc 分层多见和实用，能满足很多场景的网站开发了，逻辑再复杂点可以再加个服务层，这里基于 koa-router 进行路由的分发，从而实行 MVC 分层
  路由的配置主要由 routes/index.js 文件去自动加载其目录下的其它文件，每个文件负责相应的路由头下的路由分发，如下
routes/index.js&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import fs from 'fs'
import path from 'path'
import Router from 'koa-router'

const basename = path.basename(module.filename)
const router = Router()

fs
  .readdirSync(__dirname)
  .filter(function(file) {
    return (file.indexOf('.') !== 0) &amp;amp;&amp;amp; (file !== basename) &amp;amp;&amp;amp; (file.slice(-3) === '.js')
  })
  .forEach(function(file) {
    let route = require(path.join(__dirname, file))
    router.use(route.routes(), route.allowedMethods())
  })

export default router

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;路由文件主要负责把相应的请求分发到对应 controller 中，路由主要采用 restful 分格。
routes/articles.js&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import Router from 'koa-router'
import articles from '../controllers/articles'

const router = Router({
  prefix: '/articles'
})
router.get('/new', articles.checkLogin, articles.newArticle)
router.get('/:id', articles.show)
router.put('/:id', articles.checkLogin, articles.checkArticleOwner, articles.checkParamsBody, articles.update)
router.get('/:id/edit', articles.checkLogin, articles.checkArticleOwner, articles.edit)
router.post('/', articles.checkLogin, articles.checkParamsBody, articles.create)

// for require auto in index.js
module.exports = router
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;model 层这里基于 Sequelize 实现 orm 对接底层数据库 postgres, 利用 sequelize-cli 实现数据库的迁移功能.
例子:
user.js&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import bcrypt from 'bcrypt'

export default function(sequelize, DataTypes) {
  const User = sequelize.define('User', {
    id: {
      type: DataTypes.INTEGER,
      primaryKey: true,
      autoIncrement: true
    },
    name: {
      type: DataTypes.STRING,
      validate: {
        notEmpty: true,
        len: [1, 50]
      }
    },
    email: {
      type: DataTypes.STRING,
      validate: {
        notEmpty: true,
        isEmail: true
      }
    },
    passwordDigest: {
      type: DataTypes.STRING,
      field: 'password_digest',
      validate: {
        notEmpty: true,
        len: [8, 128]
      }
    },
    password: {
      type: DataTypes.VIRTUAL,
      allowNull: false,
      validate: {
        notEmpty: true
      }
    },
    passwordConfirmation: {
      type: DataTypes.VIRTUAL
    }
  },{
    underscored: true,
    tableName: 'users',
    indexes: [{ unique: true, fields: ['email'] }],
    classMethods: {
      associate: function(models) {
        User.hasMany(models.Article, { foreignKey: 'user_id' })
      }
    },
    instanceMethods: {
      authenticate: function(value) {
        if (bcrypt.compareSync(value, this.passwordDigest)){
          return this
        }
        else{
          return false
        }
      }
    }
  })
  function hasSecurePassword(user, options, callback) {
    if (user.password != user.passwordConfirmation) {
      throw new Error('Password confirmation doesn\'t match Password')
    }
    bcrypt.hash(user.get('password'), 10, function(err, hash) {
      if (err) return callback(err)
      user.set('passwordDigest', hash)
      return callback(null, options)
    })
  }
  User.beforeCreate(function(user, options, callback) {
    user.email = user.email.toLowerCase()
    if (user.password){
      hasSecurePassword(user, options, callback)
    }
    else{
      return callback(null, options)
    }
  })
  User.beforeUpdate(function(user, options, callback) {
    user.email = user.email.toLowerCase()
    if (user.password){
      hasSecurePassword(user, options, callback)
    }
    else{
      return callback(null, options)
    }
  })
  return User
}

&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="四、开发、测试与线上环境"&gt;四、开发、测试与线上环境&lt;/h2&gt;
&lt;p&gt;网站开发测试与部署等都会有不同的环境，也就需要不同的配置，这里我主要分了 development,test 和 production 环境，使用时用自动基于 NODE_ENV 变量加载不同的环境配置。
实现代码:
config/config.js&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var _ = require('lodash');
var development = require('./development');
var test = require('./test');
var production = require('./production');

var env = process.env.NODE_ENV || 'development';
var configs = {
  development: development,
  test: test,
  production: production
};
var defaultConfig = {
  env: env
};

var config = _.merge(defaultConfig, configs[env]);

module.exports = config;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;生产环境的配置:
config/production.js&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const port = Number.parseInt(process.env.PORT, 10) || 5000
module.exports = {
  port: port,
  hostName: process.env.HOST_NAME_PRO,
  serveStatic: process.env.SERVE_STATIC_PRO || false,
  assetHost: process.env.ASSET_HOST_PRO,
  redisUrl: process.env.REDIS_URL_PRO,
  secretKeyBase: process.env.SECRET_KEY_BASE
};

&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="五、利用中间件优化代码"&gt;五、利用中间件优化代码&lt;/h2&gt;
&lt;p&gt;koa 是以中间件思想构建的，自然代码中离不开中间件，这里介绍几个中间件的应用&lt;/p&gt;
&lt;h3 id="currentUser的注入："&gt;currentUser 的注入：&lt;/h3&gt;
&lt;p&gt;currentUser 用于获取当前登录用户，在网站用户系统上中具有重要的重要&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app.use(async (ctx, next) =&amp;gt; {
  let currentUser = null
  if(ctx.session.userId){
    currentUser = await models.User.findById(ctx.session.userId)
  }
  ctx.state = {
    currentUser: currentUser,
    isUserSignIn: (currentUser != null)
  }
  await next()
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样在以后的中间件中就可以通过 ctx.state.currentUser 得到当前用户&lt;/p&gt;
&lt;h3 id="优化controller代码"&gt;优化 controller 代码&lt;/h3&gt;
&lt;p&gt;比如 article 的 controller 里的 edit 和 update，都需要找到当前的 article 对象，也需要验证权限，而且是一样的，为了避免代码重复，这里也可以用中间件
controllers/articles.js&lt;/p&gt;
&lt;pre class="highlight hack"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;edit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="no"&gt;locals&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'编辑'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;nav&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'article'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'articles/edit'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;locals&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;let&lt;/span&gt; &lt;span class="n"&gt;article&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt;
  &lt;span class="n"&gt;article&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;articleParams&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/articles/'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;checkLogin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isUserSignIn&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;302&lt;/span&gt;
    &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;checkArticleOwner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="no"&gt;currentUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;currentUser&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="no"&gt;article&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Article&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="n"&gt;where&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;currentUser&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;article&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在路由中应用中间件&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;router.put('/:id', articles.checkLogin, articles.checkArticleOwner, articles.update)
router.get('/:id/edit', articles.checkLogin, articles.checkArticleOwner, articles.edit)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样就相当于实现了 rails 的 before_action 的功能&lt;/p&gt;
&lt;h2 id="六、webpack配置静态资源"&gt;六、webpack 配置静态资源&lt;/h2&gt;
&lt;p&gt;在没实现前后端分离前，工程代码中肯定还是少不了前端代码，现在在 webpack 是前端模块化编程比较出名的工具，这里用它来做 rails 中 assets pipeline 的功能，这里介绍下基本的配置。
config/webpack/base.js&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var webpack = require('webpack');
var path = require('path');
var publicPath = path.resolve(__dirname, '../', '../', 'public', 'assets');
var ManifestPlugin = require('webpack-manifest-plugin');
var assetHost = require('../config').assetHost;
var ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
  context: path.resolve(__dirname, '../', '../'),
  entry: {
    application: './assets/javascripts/application.js',
    articles: './assets/javascripts/articles.js',
    editor: './assets/javascripts/editor.js'
  },
  module: {
    loaders: [{
      test: /\.jsx?$/,
      exclude: /node_modules/,
      loader: ['babel-loader'],
      query: {
        presets: ['react', 'es2015']
      }
    },{
      test: /\.coffee$/,
      exclude: /node_modules/,
      loader: 'coffee-loader'
    },
    {
      test: /\.(woff|woff2|eot|ttf|otf)\??.*$/,
      loader: 'url-loader?limit=8192&amp;amp;name=[name].[ext]'
    },
    {
      test: /\.(jpe?g|png|gif|svg)\??.*$/,
      loader: 'url-loader?limit=8192&amp;amp;name=[name].[ext]'
    },
    {
      test: /\.css$/,
      loader: ExtractTextPlugin.extract("style-loader", "css-loader")
    },
    {
      test: /\.scss$/,
      loader: ExtractTextPlugin.extract('style', 'css!sass')
    }]
  },
  resolve: {
    extensions: ['', '.js', '.jsx', '.coffee', '.json']
  },
  output: {
    path: publicPath,
    publicPath: assetHost + '/assets/',
    filename: '[name]_bundle.js'
  },
  plugins: [
    new webpack.ProvidePlugin({
      $: 'jquery',
      jQuery: 'jquery'
    }),
    // new webpack.HotModuleReplacementPlugin(),
    new ManifestPlugin({
      fileName: 'kails_manifest.json'
    })
  ]
};
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="七、react前后端同构"&gt;七、react 前后端同构&lt;/h2&gt;
&lt;p&gt;node 的好处是 v8 引擎只要是 js 就可以跑，所以想 react 的渲染 dom 功能也可以在后端渲染，有利用实现 react 的前后端同构，利于 seo，对用户首屏内容也更加友好。
在前端跑 react 我就不说了，这里讲下在 koa 里面怎么实现的：&lt;/p&gt;
&lt;pre class="highlight hack"&gt;&lt;code&gt;&lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="nc"&gt;React&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;'react'&lt;/span&gt;
&lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;renderToString&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;'react-dom/server'&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="no"&gt;prerenderHtml&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;renderToString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Articles&lt;/span&gt; &lt;span class="n"&gt;articles&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;articles&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="八、测试与lint"&gt;八、测试与 lint&lt;/h2&gt;
&lt;p&gt;测试和 lint 自然是开发过程中工程化不可缺少的一部分，这里 kails 的测试采用 mocha，lint 使用 eslint
.eslintrc:&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "parser": "babel-eslint",
  "root": true,
  "rules": {
    "new-cap": 0,
    "strict": 0,
    "no-underscore-dangle": 0,
    "no-use-before-define": 1,
    "eol-last": 1,
    "indent": [2, 2, { "SwitchCase": 0 }],
    "quotes": [2, "single"],
    "linebreak-style": [2, "unix"],
    "semi": [1, "never"],
    "no-console": 1,
    "no-unused-vars": [1, {
      "argsIgnorePattern": "_",
      "varsIgnorePattern": "^debug$|^assert$|^withTransaction$"
    }]
  },
  "env": {
    "browser": true,
    "es6": true,
    "node": true,
    "mocha": true
  },
  "extends": "eslint:recommended"
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="九、console"&gt;九、console&lt;/h2&gt;
&lt;p&gt;用过 rails 的，应该都知道 rails 有个 rails console，可以已命令行的形式进入网站的环境，很是方便，这里基于 repl 实现：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (process.argv[2] &amp;amp;&amp;amp; process.argv[2][0] == 'c') {
  const repl = require('repl')
  global.models = models
  repl.start({
    prompt: '&amp;gt; ',
    useGlobal: true
  }).on('exit', () =&amp;gt; { process.exit() })
}
else {
  app.listen(config.port)
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="十、pm2部署"&gt;十、pm2 部署&lt;/h2&gt;
&lt;p&gt;开发完自然是要部署到线上，这里用 pm2 来管理：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NODE_ENV=production ./node_modules/.bin/pm2 start index.js -i 2 --name "kails" --max-memory-restart 300M --merge-logs --log-date-format="YYYY-MM-DD HH:mm Z" --output="log/production.log"
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="十一、npm scripts"&gt;十一、npm scripts&lt;/h2&gt;
&lt;p&gt;有些常用命令参数较多，也比较长，可以使用 npm scripts 里为这些命令做一些别名&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "scripts": {
    "console": "node index.js console",
    "start": "./node_modules/.bin/nodemon index.js &amp;amp; node_modules/.bin/webpack --config config/webpack.config.js --progress --colors --watch",
    "app": "node index.js",
    "pm2": "NODE_ENV=production ./node_modules/.bin/pm2 start index.js -i 2 --name \"kails\" --max-memory-restart 300M --merge-logs --log-date-format=\"YYYY-MM-DD HH:mm Z\" --output=\"log/production.log\"",
    "pm2:restart": "NODE_ENV=production ./node_modules/.bin/pm2 restart \"kails\"",
    "pm2:stop": "NODE_ENV=production ./node_modules/.bin/pm2 stop \"kails\"",
    "pm2:monit": "NODE_ENV=production ./node_modules/.bin/pm2 monit \"kails\"",
    "pm2:logs": "NODE_ENV=production ./node_modules/.bin/pm2 logs \"kails\"",
    "test": "NODE_ENV=test ./node_modules/.bin/mocha --compilers js:babel-core/register --recursive --harmony --require babel-polyfill",
    "assets_build": "node_modules/.bin/webpack --config config/webpack.config.js",
    "assets_compile": "NODE_ENV=production node_modules/.bin/webpack --config config/webpack.config.js -p",
    "webpack_dev": "node_modules/.bin/webpack --config config/webpack.config.js --progress --colors --watch",
    "lint": "eslint . --ext .js",
    "db:migrate": "node_modules/.bin/sequelize db:migrate",
    "db:rollback": "node_modules/.bin/sequelize db:migrate:undo",
    "create:migration": "node_modules/.bin/sequelize migration:create"
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样就会多出这些命令：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install
npm run db:migrate
NODE_ENV=test npm run db:migrate
# run for development, it start app and webpack dev server
npm run start
# run the app
npm run app
# run the lint
npm run lint
# run test
npm run test
# deploy
npm run assets_compile
NODE_ENV=production npm run db:migrate
npm run pm2
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="十二、更进一步"&gt;十二、更进一步&lt;/h2&gt;
&lt;p&gt;目前 kails 实现了基本的博客功能，有基本的权限验证，以及 markdown 编辑等功能.
现在目前能想到更进一步的：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;性能优化，加快响应速度&lt;/li&gt;
&lt;li&gt;Dockerfile 简化部署&lt;/li&gt;
&lt;li&gt;线上代码预编译&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;欢迎 pull request : &lt;a href="https://github.com/embbnux/kails" rel="nofollow" target="_blank" title=""&gt;https://github.com/embbnux/kails&lt;/a&gt;&lt;/p&gt;</description>
      <author>embbnux</author>
      <pubDate>Mon, 05 Sep 2016 12:22:12 +0800</pubDate>
      <link>https://ruby-china.org/topics/30987</link>
      <guid>https://ruby-china.org/topics/30987</guid>
    </item>
    <item>
      <title>生产环境使用 Docker 部署 Rails 应用 Puma 和 Sidekiq</title>
      <description>&lt;p&gt;有幸拿到 docker beta 的测试资格，在 Mac OSX 下使用 docker 更加方便好玩了。这篇博文介绍如何在生产环境也就是线上利用 docker 实现快速部署以及横向扩展，为大规模负载均衡做准备。这里使用一个 docker 容器来跑 rails 应用，另一个容器来跑异步队列 sidekiq 等服务，数据库和 redis 使用 RDS 和云 redis，直接使用 docker 镜像的数据库也可以。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;本文原文出处：&lt;a href="https://www.embbnux.com/2016/05/22/rails_use_puma_sidekiq_deploy_with_docker_on_production/" rel="nofollow" target="_blank" title=""&gt;生产环境使用 docker 部署 rails 应用 puma 和 sidekiq&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="一、生产环境使用docker前准备"&gt;一、生产环境使用 docker 前准备&lt;/h2&gt;
&lt;p&gt;首先你的 web 应用要足够干净，rails 也好，nodejs 也一样，不依赖于本地的任何东西，应该是一个 docker 镜像 pull 下来，加上一些环境变量等配置就能直接跑起来。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;数据库 url 可配置，可以连接远程数据库，或者连接其他数据库容器&lt;/li&gt;
&lt;li&gt;redis 也应该是远程连接可配置，redis 独立出去，异步队列像 sidekiq 的也可以在单独容器里跑了，因为一个 docker 容器只支持一直进程跑，所以 server 和队列是分开的，通过 redis 通讯。&lt;/li&gt;
&lt;li&gt;只处理动态流量，静态资源请走 CDN, 图片的上传也不是储存在本地磁盘的，图片的上传可以上传到容器再由容器传到云存储服务器，或者直接由客户端上传到云存储服务器，数据库里只存图片的地址就可以。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="二、配置生产环境Dockerfile"&gt;二、配置生产环境 Dockerfile&lt;/h2&gt;
&lt;p&gt;首先讲一些我的工程目录结构，主要就是 rails 的结构，这里只列出关键的文件目录结构：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;|__ app
|__ config
|__ |__ puma_docker.rb
|__ |__ database.yml
|__ |__ redis.yml
|__ |__ sidekiq.yml
|__ public
|
|__ Dockerfile
|__ Gemfile
|__ Gemfile.lock
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Dockerfile 原则应该是只添加有需要的：&lt;/p&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;##########################################&lt;/span&gt;
&lt;span class="c"&gt;# Dockerfile for rails app with puma and sidekiq postgres&lt;/span&gt;
&lt;span class="c"&gt;# Author: Embbnux Ji&lt;/span&gt;
&lt;span class="c"&gt;# HomePage: www.embbnux.com&lt;/span&gt;
&lt;span class="c"&gt;##########################################&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; ruby:2.3.1&lt;/span&gt;

&lt;span class="k"&gt;MAINTAINER&lt;/span&gt;&lt;span class="s"&gt; Embbnux embbnux@embbnux.com&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; build-essential libssl-dev libpq-dev libxml2-dev libxslt1-dev nodejs git imagemagick libbz2-dev libjpeg-dev libevent-dev libmagickcore-dev libffi-dev libglib2.0-dev zlib1g-dev libyaml-dev &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; APP_HOME /app&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="nv"&gt;$APP_HOME&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; $APP_HOME&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; Gemfile $APP_HOME/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; Gemfile.lock $APP_HOME/&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;bundle &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . $APP_HOME&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rake assets:precompile &lt;span class="nv"&gt;RAILS_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8080&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["bundle", "exec", "puma", "-C", "config/puma_docker.rb"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="三、配置rails工程"&gt;三、配置 rails 工程&lt;/h2&gt;
&lt;p&gt;我的 rails 是使用 puma 来作为 web 服务器的，docker 自然也一样，所以 app 容器默认是执行 puma 启动 server 的命令，对外输出接口为 8080, 使用 nginx 代理流量到这个服务端口即可。
puma 这里需要配置为暴露 8080 端口：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# puma_docker.rb:&lt;/span&gt;

&lt;span class="n"&gt;threads&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;
&lt;span class="n"&gt;workers&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;environment&lt;/span&gt; &lt;span class="s1"&gt;'production'&lt;/span&gt;
&lt;span class="n"&gt;bind&lt;/span&gt; &lt;span class="s1"&gt;'tcp://0.0.0.0:8080'&lt;/span&gt;
&lt;span class="n"&gt;preload_app!&lt;/span&gt;

&lt;span class="n"&gt;on_worker_boot&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:active_record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;establish_connection&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;数据库配置：&lt;/p&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# database.yml:&lt;/span&gt;

&lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;default&lt;/span&gt;
  &lt;span class="na"&gt;adapter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgresql&lt;/span&gt;
  &lt;span class="na"&gt;encoding&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unicode&lt;/span&gt;
  &lt;span class="na"&gt;pool&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
  &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5000&lt;/span&gt;

&lt;span class="na"&gt;production&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*default&lt;/span&gt;
  &lt;span class="na"&gt;pool&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
  &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= ENV["DATABASE"] %&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= ENV['DATABASE_HOST'] %&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= ENV["DATAUSER"] %&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= ENV["DATAPASSWD"] %&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;redis 的配置也一样，redis 的地址用环境变量代替：ENV["REDIS_URL"]&lt;/p&gt;
&lt;h2 id="四、使用远程仓库自动构建"&gt;四、使用远程仓库自动构建&lt;/h2&gt;
&lt;p&gt;我这边采取的远程仓库方案是 Github 加 Docker Hub，实现代码更新自动构建镜像，方法很简单，就是使用 docker hub 的自动构建功能，关联 github 仓库即可。需要在工程根目录下有一个 Dockerfile.
这样 git push 代码后过几分钟镜像就会被自动构建完成。
也可以使用 docker hub 的 webhook 功能实现构建完成自动部署，这个我暂时没测试。&lt;/p&gt;
&lt;h2 id="五、部署docker镜像到生产环境"&gt;五、部署 docker 镜像到生产环境&lt;/h2&gt;
&lt;p&gt;docker 镜像的部署很简单，直接 pull 下来跑就可以了。这里为了演示，数据库和 redis 也用一个单独的 docker 容器来跑，模拟远程连接，云储存用 docker 的 volume 功能实现，具体如下：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 下载redis镜像&lt;/span&gt;
docker pull redis
&lt;span class="c"&gt;# 下载postgres镜像&lt;/span&gt;
docker pull postgres
&lt;span class="c"&gt;# 下载已经自动构建完成的app镜像&lt;/span&gt;
docker pull embbnux/app
&lt;span class="c"&gt;# 后台运行redis容器&lt;/span&gt;
docker run &lt;span class="nt"&gt;--name&lt;/span&gt; app_redis &lt;span class="nt"&gt;-d&lt;/span&gt; redis
&lt;span class="c"&gt;# 后台运行postgres容器, 指定用户名密码&lt;/span&gt;
docker run &lt;span class="nt"&gt;--name&lt;/span&gt; app_postgres &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;password &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;user &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;app_db &lt;span class="nt"&gt;-d&lt;/span&gt; postgres
&lt;span class="c"&gt;# 后台运行app容器, 环境变量使用.env.docker文件传入, 映射容器的8080端口到本地的8080端口&lt;/span&gt;
docker run &lt;span class="nt"&gt;--env-file&lt;/span&gt; ./.env.docker &lt;span class="nt"&gt;--link&lt;/span&gt; app_redis:redis &lt;span class="nt"&gt;--link&lt;/span&gt; app_postgres:postgres &lt;span class="nt"&gt;-v&lt;/span&gt; /var/www/public/uploads:/app/public/public &lt;span class="nt"&gt;-v&lt;/span&gt; /var/log/app:/app/log &lt;span class="nt"&gt;--name&lt;/span&gt; app_web &lt;span class="nt"&gt;-p&lt;/span&gt; 127.0.0.1:8080:8080 &lt;span class="nt"&gt;-d&lt;/span&gt; embbnux/app
&lt;span class="c"&gt;# 上传assets文件到cdn&lt;/span&gt;
&lt;span class="c"&gt;# docker run --env-file ./.env.docker --link app_redis:redis --link app_postgres:postgres --name app_assets --rm embbnux/app rake cdn:upload_assets&lt;/span&gt;
&lt;span class="c"&gt;# 运行sidekiq容器&lt;/span&gt;
docker run &lt;span class="nt"&gt;--env-file&lt;/span&gt; ./.env.docker &lt;span class="nt"&gt;--link&lt;/span&gt; app_redis:redis &lt;span class="nt"&gt;--link&lt;/span&gt; app_postgres:postgres &lt;span class="nt"&gt;-v&lt;/span&gt; /var/www/public/uploads:/app/public/public &lt;span class="nt"&gt;-v&lt;/span&gt; /var/log/app:/app/log &lt;span class="nt"&gt;--name&lt;/span&gt; app_sidekiq &lt;span class="nt"&gt;-d&lt;/span&gt; embbnux/app bundle &lt;span class="nb"&gt;exec &lt;/span&gt;sidekiq &lt;span class="nt"&gt;-C&lt;/span&gt; config/sidekiq.yml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;环境变量文件如下：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# .env.docker&lt;/span&gt;
&lt;span class="nv"&gt;RAILS_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production
&lt;span class="nv"&gt;SECRET_KEY_BASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1237293729347238719422b4e25fe42a311bc4e5ffb242397934cbad3adabfbcfae4b431a5029ad6486bce777382470327493287402
&lt;span class="nv"&gt;DATABASE_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;app_postgres
&lt;span class="nv"&gt;DATABASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;app_db
&lt;span class="nv"&gt;DATAUSER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;user
&lt;span class="nv"&gt;DATAPASSWD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;password
&lt;span class="nv"&gt;REDIS_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;redis://app_redis:6379
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以后升级代码，只需要把 app pull 下来跑就可以了，多机器部署建议用 capistrano 等工具。
在开发环境使用&lt;a href="https://www.embbnux.com/2016/02/21/docker_for_rails_development_with_postgresql_and_redis/" rel="nofollow" target="_blank" title=""&gt;docker 快速构建 rails 开发环境&lt;/a&gt;可以看之前的博客。&lt;/p&gt;

&lt;p&gt;本文原文出处，Embbnux 博客，&lt;a href="https://www.embbnux.com/2016/05/22/rails_use_puma_sidekiq_deploy_with_docker_on_production/" rel="nofollow" target="_blank" title=""&gt;生产环境使用 docker 部署 rails 应用 puma 和 sidekiq&lt;/a&gt;.欢迎转载，转载请注明原文出处，并保留原文链接&lt;/p&gt;</description>
      <author>embbnux</author>
      <pubDate>Mon, 23 May 2016 19:44:42 +0800</pubDate>
      <link>https://ruby-china.org/topics/30098</link>
      <guid>https://ruby-china.org/topics/30098</guid>
    </item>
    <item>
      <title>Ruby on Rails 网站大型化之静态资源 CDN 架构</title>
      <description>&lt;p&gt;第一次发表话题，希望我新写的博客对社区有用&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;本文发表于 &lt;a href="https://www.embbnux.com/" rel="nofollow" target="_blank" title=""&gt;Embbnux 博客&lt;/a&gt;,欢迎转载，转载请注明原文出处，并保留原文链接：&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.embbnux.com/2016/01/07/ruby_on_rails_assets_cdn/" rel="nofollow" target="_blank" title=""&gt;https://www.embbnux.com/2016/01/07/ruby_on_rails_assets_cdn/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;rails 是个很成熟的网站开发架构，设计者也与时俱进把很多先进的技术与架构集成到 rails 中，造就了其他框架无法比拟的开发效率。网站发展到一定程度，网站流量越来越大就不能把静态文件请求和动态网页请求放到同一台服务器。因为静态资源的流量会远远大于动态资源的请求，流量一大，静态资源会占满服务器带宽，导致网站加载缓慢，所以 cdn 是必不可少的。&lt;/p&gt;

&lt;p&gt;rails 的开发者考虑得很全，要实现网站的 cdn 化，只需要修改一个配置文件即可，不过为了不显得我这篇博文太少了，我还是慢慢的讲来。&lt;/p&gt;
&lt;h3 id="一、讲讲原理之一个网页的请求"&gt;一、讲讲原理之一个网页的请求&lt;/h3&gt;
&lt;p&gt;一个网页从在浏览器输入网址，到展示在你面前，之间经历了好多个请求，主要流程如下：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;发送请一个请求到所访问地址，网站服务器返回一个 html 文本&lt;/li&gt;
&lt;li&gt;浏览器解析 html 文本，并加载里面引入的 js、css 以及图片等文件，这些叫做静态资源，同样浏览器还要往服务器发送这些静态资源的请求&lt;/li&gt;
&lt;li&gt;等待外部静态资源请求完成后，浏览器在进行 css,js 文件的解析执行，把图片等填充到网页上&lt;/li&gt;
&lt;li&gt;然后就是你所看到的一个网页了，有颜色有图片也有交互&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="二、rails的静态资源"&gt;二、rails 的静态资源&lt;/h3&gt;
&lt;p&gt;rails 对于的静态资源主要有两种，一种是写在代码里的，比如网站的 js、css 以及图标等文件，还有一种为用户上传的，比如用户上传的头像图片等。&lt;/p&gt;

&lt;p&gt;对于静态文件等 rails 引入了&lt;a href="http://guides.rubyonrails.org/asset_pipeline.html" rel="nofollow" target="_blank" title=""&gt;Asset Pipeline&lt;/a&gt;用来管理编译静态资源，对于第一种写在代码里的静态文件，在 rails 工程里面是存在 app/assets 文件夹下，分别有 images 和 stylesheets 以及 javascripts 等分类文件夹，js 和 css 代码放在这里可以用 coffeescript 以及 sass 等各种语言来写，最终线上环境 (生产环境) 得对这些代码进行编译生成 js 以及 css 文件，这样浏览器才会识别，编译不仅进行格式的翻译还会进行 minify 等，最终会在编译文件后面加一段当前文件的 hash，所以代码变动，编译出的文件就不一样：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;RAILS_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production rake assets:precompile
&lt;span class="c"&gt;#assets/javascripts/application.js =&amp;gt; pubilic/application-3214abdc8899.js&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;由于编译完的文件被加了一段 hash 所以不能直接在 html 里面用路径访问，所以得在 view 层渲染时得出文件路径：&lt;/p&gt;

&lt;p&gt;app/views/layout/application.html.erb:&lt;/p&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;stylesheet_link_tag&lt;/span&gt; &lt;span class="s1"&gt;'application'&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;javascript_include_tag&lt;/span&gt; &lt;span class="s1"&gt;'application'&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;image_url&lt;/span&gt; &lt;span class="s1"&gt;'favicon.png'&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对应渲染出来的 html 是：&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;media=&lt;/span&gt;&lt;span class="s"&gt;"screen"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/assets/application-122fe15eeed76211bd37e2f1234454.css"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/assets/application-583bffd2a21c2a6b8d1ab72bad4ba8af.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nx"&gt;assets&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;favicon&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;aa0e2adc41f64de39&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;png&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;加 hash 是为了在代码得到更新后浏览器能够及时更新使用新的静态文件，早期的 rails 版本使用的版本控制手段如下：&lt;/p&gt;

&lt;p&gt;/stylesheets/application.css?1309495796&lt;/p&gt;

&lt;p&gt;但是这种手段在 cdn 的使用场景上不能及时更新存在 cdn 上的文件，所以该用加了 hash 的文件名来做版本控制，保证 cdn 部署时代码和静态资源得到同时跟新，变成这样&lt;/p&gt;

&lt;p&gt;application-1309495796.css&lt;/p&gt;

&lt;p&gt;第二种静态资源是用户上传的，这些的不是放在代码里的，在 rails 工程中这些静态文件放在 public 文件夹下，因为 web 服务器的根目录指向的就是 public 文件夹，其他文件夹浏览器没有权限访问得到，之前的 js 和 css 也得被编译完后放到 public 文件夹才可以访问。这些静态文件不会被编译，所以文件名后面不会被加入 hash, 但也可以用 image_url‘upload/avatar.png’来访问，image_url 会自动区分要不要加 hash.&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;upload/avatar.png
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="三、开启cdn"&gt;三、开启 cdn&lt;/h3&gt;
&lt;p&gt;说了这么多，是时候开启 cdn 了。如果一直是按 rails 规范来写的话在这里开启 cdn 配置，只需要在 config/environments/production.rb 加一句话：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;action_controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;asset_host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'static-cdn.embbnux.com'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样之前渲染出来的 html 就变成这样：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;link&lt;/span&gt; &lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"stylesheet"&lt;/span&gt; &lt;span class="n"&gt;media&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"screen"&lt;/span&gt; &lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"http://static-cdn.embbnux.com/assets/application-122fe15eeed7688837e2f1234454.css"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"http://static-cdn.embbnux.com/assets/application-583bffd2a21c2a6b8d1a888ad4ba8af.js"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="ss"&gt;:/&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;static&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;cdn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;embbnux&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;comassets&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;favicon&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;aa0e2adc41888de39&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;png&lt;/span&gt;
&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="ss"&gt;:/&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;static&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;cdn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;embbnux&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;upload&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;avatar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;png&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;出来的路径就是指向 cdn 服务器上的静态资源了，这样一个网页的访问就只会有第一个 html 的请求发到我们服务器上，其他的静态资源请求是发到 cdn 服务器上的，一个 html 文本一半也就几 k，大小很小的，加载时间也很快，不很会占用服务器带宽。&lt;/p&gt;

&lt;p&gt;有时候为了区分和管理第一种网站静态资源和第二种用户静态资源可以配置分别指向两个 cdn 域名，不同的域名用不同的 cdn 空间，可以这样配置&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;action_controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;asset_host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Proc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=~&lt;/span&gt; &lt;span class="sr"&gt;/assets/&lt;/span&gt;
    &lt;span class="s1"&gt;'static-assets-cdn.embbnux.com'&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="s1"&gt;'static-images-cdn.embbnux.com'&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样渲染出来就变成这样：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;link&lt;/span&gt; &lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"stylesheet"&lt;/span&gt; &lt;span class="n"&gt;media&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"screen"&lt;/span&gt; &lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"http://static-assets-cdn.embbnux.com/assets/application-122fe15eeed76211bd37e2f1234454.css"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"http://static-assets-cdn.embbnux.com/assets/application-583bffd2a21c2a6b8d1882bad488af.js"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="ss"&gt;:/&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;static&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;assets&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;cdn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;embbnux&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;comassets&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;favicon&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;aa0e2adc8884de39&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;png&lt;/span&gt;
&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="ss"&gt;:/&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;static&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;images&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;cdn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;embbnux&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;upload&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;avatar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;png&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;为了保证 assets 文件及时更新到 cdn 存储，可以写个 rake 脚本利用 cdn 商提供的接口，在部署的时候及时上传 assets 文件到 cdn 空间比如：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rake cdn:assets_upload
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="四、web服务器配置"&gt;四、web 服务器配置&lt;/h3&gt;
&lt;p&gt;为了保证 cdn 的文件能够及时与我们自己服务器上静态文件保持一致，需要开启 cdn 的镜像功能，但为了不使 cdn 缓存我们的动态 html 内容，造成镜像网站而降低网站权重，需要配置一下 nginx 服务器&lt;/p&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;server&lt;/span&gt; {
 &lt;span class="n"&gt;server_name&lt;/span&gt; &lt;span class="n"&gt;www&lt;/span&gt;.&lt;span class="n"&gt;embbnux&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt;;
 &lt;span class="n"&gt;root&lt;/span&gt; /&lt;span class="n"&gt;rails_app&lt;/span&gt;/&lt;span class="n"&gt;public&lt;/span&gt;;
 &lt;span class="n"&gt;location&lt;/span&gt; / {
   &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;.&lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;.&lt;span class="n"&gt;htm&lt;/span&gt;;
   &lt;span class="n"&gt;proxy_pass&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;://&lt;span class="n"&gt;rails_app_upstream&lt;/span&gt;;
   &lt;span class="n"&gt;proxy_set_header&lt;/span&gt; &lt;span class="n"&gt;Host&lt;/span&gt; $&lt;span class="n"&gt;host&lt;/span&gt;;
   &lt;span class="n"&gt;proxy_set_header&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;-&lt;span class="n"&gt;Forwarded&lt;/span&gt;-&lt;span class="n"&gt;For&lt;/span&gt; $&lt;span class="n"&gt;proxy_add_x_forwarded_for&lt;/span&gt;;
 }
}

&lt;span class="n"&gt;server&lt;/span&gt; {
 &lt;span class="n"&gt;server_name&lt;/span&gt; &lt;span class="n"&gt;static&lt;/span&gt;.&lt;span class="n"&gt;embbnux&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt;;
 &lt;span class="n"&gt;root&lt;/span&gt; /&lt;span class="n"&gt;rails_app&lt;/span&gt;/&lt;span class="n"&gt;public&lt;/span&gt;;
 &lt;span class="n"&gt;location&lt;/span&gt; ~* \.(&lt;span class="n"&gt;js&lt;/span&gt;|&lt;span class="n"&gt;css&lt;/span&gt;|&lt;span class="n"&gt;png&lt;/span&gt;|&lt;span class="n"&gt;jpg&lt;/span&gt;|&lt;span class="n"&gt;jpeg&lt;/span&gt;|&lt;span class="n"&gt;gif&lt;/span&gt;|&lt;span class="n"&gt;ico&lt;/span&gt;)$ {
   &lt;span class="n"&gt;expires&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt;;
   &lt;span class="n"&gt;log_not_found&lt;/span&gt; &lt;span class="n"&gt;off&lt;/span&gt;;
 }
 &lt;span class="n"&gt;location&lt;/span&gt; ~* ^/&lt;span class="n"&gt;assets&lt;/span&gt;/ {
   &lt;span class="n"&gt;root&lt;/span&gt; /&lt;span class="n"&gt;rails_app&lt;/span&gt;/&lt;span class="n"&gt;public&lt;/span&gt;;
   &lt;span class="n"&gt;expires&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;;
   &lt;span class="n"&gt;add_header&lt;/span&gt; &lt;span class="n"&gt;Cache&lt;/span&gt;-&lt;span class="n"&gt;Control&lt;/span&gt; &lt;span class="n"&gt;public&lt;/span&gt;;
   &lt;span class="n"&gt;add_header&lt;/span&gt; &lt;span class="n"&gt;Last&lt;/span&gt;-&lt;span class="n"&gt;Modified&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;;
   &lt;span class="n"&gt;add_header&lt;/span&gt; &lt;span class="n"&gt;ETag&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;;
   &lt;span class="n"&gt;break&lt;/span&gt;;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样服务器上的静态资源就可以通过 static.embbnux.com 访问，动态资源只能通过 www.embbnux.com 访问，cdn 镜像通过 static 域名，也就不用担心会镜像动态页面&lt;/p&gt;
&lt;h3 id="四、更进一步"&gt;四、更进一步&lt;/h3&gt;
&lt;p&gt;开启 cdn 后也就不用担心静态资源来占我们服务器的带宽了，不过这也只是大型化网站的第一步，还可以进行很多优化，欢迎拍砖。流量在大一点，只接受动态资源请求的我们网站服务器也会承受不了的，这时候就是开启负载均衡的时候了，可以一个 nginx 代理请求，然后中转到几台服务器上的 rails_app_upstream，这都是后话了。&lt;/p&gt;

&lt;p&gt;更多内容欢迎查看原文博客:
 &lt;a href="https://www.embbnux.com/2016/01/07/ruby_on_rails_assets_cdn/" rel="nofollow" target="_blank" title=""&gt;https://www.embbnux.com/2016/01/07/ruby_on_rails_assets_cdn/&lt;/a&gt;&lt;/p&gt;</description>
      <author>embbnux</author>
      <pubDate>Fri, 08 Jan 2016 23:49:51 +0800</pubDate>
      <link>https://ruby-china.org/topics/28666</link>
      <guid>https://ruby-china.org/topics/28666</guid>
    </item>
  </channel>
</rss>
