一、app 模块介绍

二、app 模块的启动和代码解读

2.1 app 模块的启动方式

启动方式可以参考文档中给出的 docker 启动命令,下面只摘出主要的部分:

stf app --port 3000 \
  --auth-url https://stf.example.org/auth/mock/ \
  --websocket-url wss://stf.example.org/

也就是说,只要指定三个参数 port、授权--auth-url、websocket 地址--websocket-url 即可,auth 和 websocket 的参数会在启动对应的模块时指定。这里的 auth 参数的作用是在 app 模块中授权失败的时候自动跳转么授权(登录)页面,而 websocket 的 url 似乎只是在 GLOBAL_APPSTATE 中用到,没有用来建立连接。

这里面的"stf"这个命令可以执行是因为我们把 bin/stf 这个文件加入到了系统环境变量,打开 bin/stf 这个文件可以看出只有一行../lib/cli/please,然后我们看看这个 please 是什么,发现它其实指向了../lib/cli/index.js,因此,你如果没有把 bin/stf 加入到系统环境变量,直接用 node ../lib/cli/index.js 也可以,或者直接 node ../lib/cli,例如 local 启动可以直接

node ../lib/cli local --public-ip xxx

或者这样启动 app

node ../lib/cli app --port 3000 \
  --auth-url https://stf.example.org/auth/mock/ \
  --websocket-url wss://stf.example.org/

2.2 代码解读

首先看 index.js 文件,这是 app 模块的入口。在 app 模块中引入了不少其他的模块,下面简单说一下,其实如果你了解过 express 框架,这些都很简单了。

app.set 是设备服务器的一些参数:

app.use(路径,function(){}) 就是处理对应路径的一些方法了,比如说/static/wiki、/static/app/build/entry、/static/app/data 等,假如用户请求的是/static/logo 这个路径,那么 express 会把请求交给 serveStatic 这个方法来处理。详情可以查询一下 express 的中间件相关知识。

看下面这一段:

if (fs.existsSync(pathutil.resource('build'))) {
  log.info('Using pre-built resources')
  app.use(compression())
  app.use('/static/app/build/entry',
    serveStatic(pathutil.resource('build/entry')))
  app.use('/static/app/build', serveStatic(pathutil.resource('build'), {
    maxAge: '10d'
  }))
}
else {
  log.info('Using webpack')
  // Keep webpack-related requires here, as our prebuilt package won't
  // have them at all.
  var webpackServerConfig = require('./../../../webpack.config').webpackServer
  app.use('/static/app/build',
    require('./middleware/webpack')(webpackServerConfig))
}

这段话的意思是如果存在 build 文件夹(已经用 webpack build 过),那么就使用 build 文件夹中的内容,否则就要使用 webpack 热生成了。关于 webpack 中间件下文再做介绍。

app.use(cookieSession({
  name: options.ssid
, keys: [options.secret]
}))

app.use(auth({
  secret: options.secret
, authUrl: options.authUrl
}))

cookieSession 需要设置 name 和 keys 两个参数,key 是用来对 cookies 进行签名和验证用的。

auth 方法也需要传入两个参数 secret 和 authUrl,authUrl 是指授权 url,在授权模块中会指定。

下面看一下 middleware 文件夹中的 auth 文件,在这里定义了 auth 方法。先看第一段话:

if (req.query.jwt) {
  // Coming from auth client
  var data = jwtutil.decode(req.query.jwt, options.secret)
  var redir = urlutil.removeParam(req.url, 'jwt')
  if (data) {
    // Redirect once to get rid of the token
    dbapi.saveUserAfterLogin({
        name: data.name
      , email: data.email
      , ip: req.ip
      })
      .then(function() {
        req.session.jwt = data
        res.redirect(redir)
      })
      .catch(next)
  }
  else {
    // Invalid token, forward to auth client
    res.redirect(options.authUrl)
  }
}

if (req.query.jwt) 是指 query 中包含 jwt 的字段,query 中的 jwt 字段是指 url 中直接包含 jwt=xxxx 等内容,在用户每一次用 mock 方式登录的时候会出现这种情况。下面就是把 jwt 中的内容解析为明文信息 data,然后是解析 jwt 中的重定向 url 到 redir。如果发现 jwt 解析成功,就把对应的用户存储在数据库中,如果解析不成功,就重定向到授权的 url。

总结一下,这段代码其实是处理用户第一次登录的时候的验证问题,用户第一次登录的时候由于 cookies 没有 jwt token,只能由 auth 模块在 url 的后面加入 jwt 参数来授权,然后由 app 模块解析。

else if (req.session && req.session.jwt) {
  dbapi.loadUser(req.session.jwt.email)
    .then(function(user) {
      if (user) {
        // Continue existing session
        req.user = user
        next()
      }
      else {
        // We no longer have the user in the database
        res.redirect(options.authUrl)
      }
    })
    .catch(next)
}

在 else 语句中,是从 session 中解析了用户信息。一个典型的 jwt 串如下:

{ jwt: 'eyJhbGciOiJIUzI1NiIsImV4cCI6MTUwMTQxMTAzNzg5NH0.eyJlbWFpbCI6InRlc3R1c2VyQHRlc3QuY29tIiwibmFtZSI6InRlc3R1c2VyIn0.W5zYDcA4wu6kB1GWR9BLOKdGtyDwRO9IQaA2LqW7CrY' }

下面这段代码:

app.all('/app/api/v1/dummy', function(req, res) {
  res.send('OK')
})

我也没搞清楚是干什么用的,估计是测试用的。

下面的 bodyParser、csrf、validator 可以参数对应的中间件。res.cookie 是把 XSRF-TOKEN 写入 cookies,然后在请求的时候带上,防止伪造请求。

app.get('/', function(req, res) {
  res.render('index')
})

这段就是用户访问 stf 的根目录时处理的代码了,这里返回了 index,是指 res/app/views/index.pug 这个文件。

app.get('/app/api/v1/state.js', function(req, res) {
    ...xxxx
})

这段是提供 app 状态的代码,它的后缀有点儿奇怪,是.js,当别人访问它的时候,会认为他是一个 js 文件,但是不真的是 js 文件,而且在 response 里设置 type 为'application/javascript',应该是为了动态设置前端某个静态变量用的,等我研究透了 STF 前端再详细介绍。在 res/app/views/index.pug 有用到这个路径。

2.3 app 模块的访问路径

app 模块的访问路径就是 STF 网站的根目录。下面是 app 模块的 nginx 配置:

location / {
  proxy_pass http://stf_app;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_set_header X-Real-IP $http_x_real_ip;
}

可以看出 app 的访问路径是/。

三、app 模块简单总结


↙↙↙阅读原文可查看相关链接,并与作者交流