Jun 20, 2017 - R server & nginx

R的服务器

托福于Rstudio的httpuv包,R语言也终于可以做后端了。httpuv的底层是两个C语言库libuv和http-parser,所以在吞吐量上并不逊色Python的web框架。

httpuv完成了基础的HTTP和WebSocket请求的处理接口,基于它二次开发的框架有fiery和plumber。当然shiny也算,不过shiny的前后端集成度太高,并不适合做为单纯的后端使用。

一个fiery的例子:R的高性能 http 服务 fiery

fiery和plumber的简单对比:Benchmark of R’s http framework

Nginx

Nginx是一个开源的HTTP Server,同时支持转发,代理,权限认证和负责均衡等功能。在Nginx的帮助下,可以轻易的在一台服务器上配置多个网站和应用程序。

在使用R语言的框架搭建好一个简易APP之后,可以使用nginx来为他配置上你的域名。以下是一个简单的配置文件,假定有两个APPs在监听9123和9124端口,下边的配置可以将访问serverr.com的请求均匀的转发到9123和9124端口。

upstream rserver {
    server 127.0.0.1:9123;
    server 127.0.0.1:9124;
}

server {
    server_name serverr.com;  # config host name
    location / {
        proxy_pass http://rserver;
    }
}

把以上配置写入rserver.conf文件中,并放在nginx的配置文件里,重启nginx使之生效。此处以及后文假定系统为Ubuntu。

sudo cp rserver.conf /etc/nginx/conf.d
sudo nginx -s reload

如果serverr.com是你自己的域名,你需要到域名提供商那里设置DNS将该域名指向你的主机IP。为了简单起见,可以修改本地的hosts文件,将serverr.com域名指向自己:

sudo vi /etc/hosts
# 添加这样一行信息:  127.0.0.1 serverr.com

设置完成后,就可以通过curl http://serverr.com/predict?val=190访问了。可以使用siege工具来测试效果:

siege -c 255 -r 10 'http://serverr.com/predict?val=190'
siege -c 255 -r 10 'http://localhost:9123/predict?val=190'
siege -c 255 -r 10 'http://localhost:9124/predict?val=190'

这里使用nginx的测试结果稍微好一点点,但效果并不十分明显。因为数据的处理太过简单,主要在于IO的耗时。相比于直接访问端口,访问域名会需要额外的一点时间来解析并转发。使用nginx的负载均衡的另一个优点在于,当其中一个程序宕机时,nginx会自动把请求转发到另一个健康的程序上去,保证服务不间断。

最后介绍一些安全措施:

  1. 参考Shiny 工程化实践之HTTPS加密中的nginx设置,为你的网站配置https。

  2. 参考RESTRICTING ACCESS WITH HTTP BASIC AUTHENTICATION为APP设置密码

  3. 通过iptables规则,设置特定IP才能访问。

以下规则使得只有192.168.1.1到192.168.1.255的IP地址可以访问本机的9123和9124端口,杜绝其他外来流量。

sudo iptables -I INPUT -p TCP --dport 9123 -j DROP
sudo iptables -I INPUT -p TCP --dport 9124 -j DROP
sudo iptables -I INPUT -m iprange --src-range 192.168.1.1-192.168.1.255 -p TCP --dport 9123 -j ACCEPT
sudo iptables -I INPUT -m iprange --src-range 192.168.1.1-192.168.1.255 -p TCP --dport 9124 -j ACCEPT

Mar 12, 2017 - Tornado & SPA(Single Page Application)

前端领域SPA(Single Page Application)如火如荼,一个SPA也包含了路由的概念和简单的数据处理过程。简单来说就是一个单文件比如index.html,包含了所有的网站内容。它自身的路由使得你可以看到浏览器中url的变化,但实际并没有发生网络请求。因为第一次打开网站时就加载了所有组件。这样在网站内部来回穿梭就如丝般顺滑,是极佳的体验。遇到这样的网站切勿点击刷新。

回到开发上来,前后端分离使得开发相对独立,后端致力于提供API返回数据,后端致力于数据的展现。而在部署时,目前流行的方式似乎是前后端各开一个独立的服务进程,但对一个全栈来说调试起来未免有点复杂。前端再如何进化,最后的产品始终是HMTL,CSS和Javascript的结合,完全可以作为静态文件在浏览器中渲染。所以在后端中加一个路由用来渲染前端生成的文件就可以了。

问题

问题在于,SPA有自己独立的路由,后端也有数据接口的路由,二者很容易搞混。 比如下边的路由设置:

router = [
     (r"/api/(.*)", APIHandler)
     (r"/(.*)", StaticFileHandler)  # StaticFileHandler 是一个渲染静态文件的类
]

其中/api是后端的数据接口,其他是SPA的前端页面。

访问/api/...时会调用后端的数据接口APIHandler,其他情况下调用StaticFileHandler渲染静态文件。而SPA有自己的路由,比如/class/course1。然而当访问这个路由时,会首先被后端拦截并匹配到相应的StaticFileHandler,去寻找class文件夹中的course1文件。这个目录当然是不存在的,于是返回404错误。

理想状态应该是所有非/api开头的路由,都转发到SPA相应的页面中去。注意这里就千万不要在SPA中也设置api路由了,否则永远也转不过去。

Django方案

这篇博客URL Routing for a Decoupled App, with Angular 2 and Django介绍了如何将Django的后端与Angular2的前端无缝结合。

tornado方案

在tornado中,有StaticFileHandler模块可以渲染静态文件。基于这个模块定义一个子类,将所有非js和css文件的路由都重定向到主页面。

class Ag2Handler(StaticFileHandler):
    @gen.coroutine
    def get(self, path, *args, **kwargs):
        if path.strip('/').split('.')[-1] not in ['js', 'css']:
            path = 'index.html'
        yield super(Ag2Handler, self).get(path, *args, **kwargs)

router = [
    (r"/api/(.*)", APIHandler),
    (r"/(.*)", Ag2Handler, {'default_filename': 'index.html', 'path': 'XXXXXX'})
]

访问/class/resume页面时,在后端被最后一个路由r"/(.*)"匹配到。使用Ag2Handler渲染,强行重定向到主页index.html,完工。

Feb 27, 2017 - xaringan定制主题

xaringan是基于remark.js开发的幻灯片写作工具。

  • 优势在于可能更方便的嵌入R语言代码和相应的输出。其实也是可以支持Python,SQL和Bash等语言的,如果你使用RStudio的话。

  • 劣势在于,相对传统的PPT或者Keynote来说,在样式的调整上稍微有点门槛。你需要懂点HTML,CSS和Javascript才能游刃有余。

使用xaringan之后,一些简单的样式调整都可以通过CSS来实现。突发奇想的一个需求是,有没有办法在每一页幻灯片加上一个Logo?

查看一个编译后的幻灯片源码,你会发现每张片子都包含在一个class为remark-slide-container的div元素。而所有的正文都在class为remark-slide-content的div元素里。

那如何自动在所有这些div生成时加入一个DOM元素?这里的元素可以是logo或者任意其他东西,遗憾的是目前似乎没办法。回过头看remark.js的文档,目前并不支持自定义幻灯片的模板(也可能是我没找到)。但我们知道Javascript最是擅长操作DOM元素的增删改,所以可以在编译后的幻灯片里做些手脚,插入一些有趣的东西。

回到xaringan来,它依然是使用rmarkdown这个包来编译的,编译时有includes参数可以在文档的header标签里或者body标签前后插入元素。

output:
  xaringan::moon_reader:
    seal: FALSE
    lib_dir: libs
    css: header.css
    includes:
      after_body: header.js

这里我们使用自定义的header.css文件,并在body标签最后插入一个header.js文件,内容如下:

<script src=jquery.min.js></script>
<script>
(function () {
  $('.remark-slide-content').prepend('<div class="nyc-header" />');
})();
</script>

其中先是引入Jquery模块,接着使用Jquery的能力在所有remark-slide-content标签头部插入一个class为nyc-header的元素。这个元素就用来承载我们的Logo,接下来就全都是CSS的事情了。

header.css文件中定义nyc-header的样式,包括背景色,背景图片和位置等:

.nyc-header{
  background-color: #00a3af;
  background-image: url(logo.png);
  background-position: center center;
  background-repeat: no-repeat;
  background-size: auto;
  width: 100%;
  height: 8%;
  position: absolute;
  left: 0px;
  top: 0px;
}

最后的效果如下图,每页都会有顶层的logo。