STF openstf 模块解读--app

blueshark · September 27, 2017 · Last by 陈子昂 replied at September 29, 2017 · 3203 hits
本帖已被设为精华帖!

一、app 模块介绍

  • app 模块位于 lib/units/app/路径下,app 模块的作用是提供一个完整的 http 服务器,这里的『完整』的意思是指包括 html、js、css、image 等所有的 web 静态内容。如果你深入了解过 stf 前面的实现原理,就会知道 stf 是一个完全的前后端分离式设计,简单来说,动态的数据完全是从接口来获取的,页面渲染完全由 js 来完成,app 模块的作用就是把前端的所有东西返回给浏览器,返回完以后,前端面就和 app 模块没有任何交互了!!!如果你不相信,可以在打开 stf 首页以后把 app 模块停掉(需要用 docker 等分离式部署方式),完全不会影响 stf 的正常使用,除非你刷新页面。

  • app 模块从原理上讲非常简单,它就是一个普通的 http 服务器,使用 express 来实现,而它所需要返回的东西已经由 webpack 打包好了。

  • 这里多说一句,如果你在开发 stf 的时候,用 local 方式启动,只是对前端做了修改,只需要执行 npm install 一下然后刷新页面就可以了,不用重新启动整个 stf。

二、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 框架,这些都很简单了。

  • http:node 的表态网页服务器,最新版的 express 似乎不用这个来创建服务器了。
  • url:用来解析 url 的一个工具,由 nodejs 直接提供。其中 url.parse 可以将 url 字符串转换为对象。
  • fs:文件系统,在这里用来判断某个文件(夹)是否存在。
  • express:不用说了,用来创建服务器的。
  • express-validator:express 的验证器,主要用来验证用户的输入是否合法,这里还没看出有什么用。
  • cookie-session:是一个基于 cookies 的 session 中间件。
  • body-parser:用来解析 expess 的 body 参数的中间件,如果你的 http 写了 Content-Type 为 application/json,那么就需要 bodyParser.json() 来解析。
  • serve-favicon:这个是处理网站 logo 的一个中间件,由于 logo 被经常访问,serve-favicon 可以加快 logo 的访问速度,stf 的 logo 文件是 STF-128.png。
  • serve-static:处理 express 表态资源的一个中间件,允许指定服务器的某个路径为本机的某个静态资源文件夹。
  • csrf:这是一个防止跨站点伪造请求的中间件,可以防止别人利用 cookies 来攻击你的网站,具体可以搜索 CSRF 的攻击与防御。
  • compression:提供 web 的压缩功能,比如说 gzip 等。
  • logger:打出对应模块的 log,会在其他文章中详细解释这个 logger 的用途。
  • pathutil:路径一个工具,主要用途是给对应的目录加上对应的路径,这个在后面的文章中再做详细介绍。
  • auth:这是一个控制授权的工具,下面再详细介绍。
  • deviceIconMiddleware、browserIconMiddleware、appstoreIconMiddleware 处理对应 icon 的中间件,其实就是把对应的 icon 的路径指到对应的 node_module 中。
  • markdownServe:一个用来展示 markdown 文件的工具,在主页面的 wiki 中会用到。

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

  • pug:express 服务器的模板引擎为 pug,了解 exress 的同学都知道 express 其实支持很多种模板的。
  • views:指 express web 视图的路径。
  • strict routing:这个是 http 的一个模式,我也没深入了解。
  • case sensitive routing:是否区分路径中的大小写。
  • trust proxy:信任代理?比如用 nginx 做了代理,那么后端应用就不能直接获取到用户的 ip,这时候可以通过「X-Forwarded-」来获取用户真实的 ip,不过这个很容易伪造。

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 模块简单总结

  • 当我们执行 stf app xxx 的时候,yargs 这个命令行工具会执行 lib/units/app/index.js 这个文件,然后会启动一个 express 服务器。如果用户访问 stf 网站的根目录,express 服务器会返回 webpack 打包好的 html、css、js、img 等文件,其中 js 会建立 websocket 或者请求 api 与后端交互。在 app 中也包含了 cookies 和授权等内容。

  • app 本身其实并不复杂,而它向浏览器传输的内容 -- 前端框架则是 STF 中非常复杂的一部分。

共收到 2 条回复 时间 点赞
思寒_seveniruby 将本帖设为了精华贴 28 Sep 10:42

不错 这样的工具解读可以多来点

写得不错。

simple 专栏文章:[精华帖] 社区历年精华帖分类归总 中提及了此贴 13 Dec 20:49
需要 Sign In 后方可回复, 如果你还没有账号请点击这里 Sign Up