在Ruby中使用DATA和__END__将代码和数据混合

之前一直不理解 __END__ 的用法,现在看了这篇文章后才算是了解了,于是便翻译之。
《Mixing code and data in Ruby with DATA and __END__》: http://blog.honeybadger.io/data-and-end-in-ruby/


你知道 Ruby 提供了一种方法在你的脚本中可以将源文件作为数据源来使用吗?当你在写一些一次性的脚本用于验证概念时这个小技巧会为你节约一些时间。让我们来看看吧。

DATA 和 __END__

在下面这个例子中,我使用了一个有趣的关键字 __END__。所有在 __END__ 下面的内容将会被 Ruby 解释器所忽略。但是有趣的是 ruby 为你提供了一个称为 DATA 的 IO 对象,就像你可以读取其他任何文件一样,它能让你读取到 __END__ 以下的所有内容。

下面这个例子中,我们遍历每一行并进行输出。

1
2
3
4
5
6
7
8
DATA.each_line do |line|
puts line
end

__END__
Doom
Quake
Diablo

关于这个技术我最喜欢的实例是使用 DATA 来包含一个 ERB 模板。它同样也可用于 YAML、CSV等等。

1
2
3
4
5
6
7
8
require 'erb'

time = Time.now
renderer = ERB.new(DATA.read)
puts renderer.result()

__END__
The current time is <%= time %>.

实际上你也可以使用 DATA 来读取 __END__ 关键字以上的内容。那是因为 DATA 实际上是一个指向了整个源文件,并定位到 __END__ 关键字的位置。你可以试试看在输出之前将 IO 对象反转。下面这个例子将会输出整个源文件。

1
2
3
4
5
DATA.rewind
puts DATA.read # prints the entire source file

__END__
meh

多文件问题

这个技术最大的缺点是它只能用于单个文件的脚本,直接运行该文件,不能在其他文件进行导入。

下面这个例子,我们有两个文件,并且每个都有它们自己的 __END__ 部分。然而却只有一个全局 DATA 对象。因此第二个文件的 __END__ 部分刚访问不到了。

1
2
3
4
5
6
7
8
9
10
# first.rb
require "./second"

puts "First file\n----------------------"
puts DATA.read

print_second_data()

__END__
First end clause
1
2
3
4
5
6
7
8
9
10
# second.rb

def print_second_data
puts "Second file\n----------------------"
puts DATA.read # Won't output anything, since first.rb read the entire file
end

__END__

Second end clause
1
2
3
4
5
6
7
snhorne ~/tmp $ ruby first.rb
First file
----------------------
First end clause

Second file
----------------------

对于多文件的一个解决方案

在 Sinatra 中有一个很酷的特性是它允许你在你应用的 __END__ 部分添加多个内联模板。它看起来像下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# This code is from the Sinatra docs at http://www.sinatrarb.com/intro.html
require 'sinatra'

get '/' do
haml :index
end

__END__

@@ layout
%html

= yield

@@ index
%div.title Hello world.

sinatra 是如何实现的呢?毕竟你的应用可能是运行在 rack 上。在生产环境中你不能再通过 ruby myapp.rb 来运行!他们必须有一种在多文件中使用 DATA 的解决方案。

因此如果你稍微深入一下 Sinatra 的源代码,你会发现它们并没有使用 DATA。而是使用了跟下面这段代码类似的方案。

1
2
# I'm paraphrasing. See the original at https://github.com/sinatra/sinatra/blob/master/lib/sinatra/base.rb#L1284
app, data = File.read(__FILE__).split(/^__END__$/, 2)

实际上它比这个要更复杂一些,因为它们不想读取 __FILE__,它只是 sinatra/base.rb 文件。它们其实是需要获取调用了该方法的文件的内容。它们通过解析 caller 的结果来获取。

caller 方法将会告诉你当前运行的方法是从哪调用的。这里是个简单的例子:

1
2
3
4
5
def some_method
puts caller
end

some_method # => caller.rb:5:in `<main>'

现在可以简单地获取到文件名了,然后从该文件中再提取出与 DATA 等价的内容。

1
2
3
def get_caller_data
puts File.read(caller.first.split(":").first).split("__END__", 2).last
end

请善用它,不要作恶

希望对于这些技巧你不要经常使用。它们不会使得代码干净、可维护。

然后,你偶尔需要一些又快又脏的实现一个一次性的脚本或者验证一些概念。此时 DATA__END__ 就非常有用了。

Mac下使用supervisor进行服务管理

最近刚切换到 Mac 平台上,感觉各种不适应。之前使用 Ubuntu 时,有 service 命令可以对服务进行管理,
但是在 Mac 系统下没有对应的工具。也许有人说可以用 launchctl 啊。但是 launchctl 的服务是开机自动启动的,
而我又不想要开机自动启动,只想在需要时启动,使用完后就停止。

由于没有相应的工具,因此我只得在终端下通过命令来启动服务,但是这个又得一直打开着一个新的终端标签。
对于有洁癖的我来说,表示很不爽。本来想自己写个脚本来管理的,但是这个又得针对每个服务写个脚本,也很麻烦。
正在纠结的时候想起了还有 supervisor 可以用。

supervisor 是使用 python 开发的一个后台服务管理程序。

首先使用 brew 安装 python 工具: brew install python,并覆盖掉系统自带的 python。
因为我有洁癖不想将软件安装在系统目录中,因此就再单独安装一个 python。
若对此不在意的可跳过此步。

然后再安装 supervisor: pip install supervisor
supervisor 不支持 python3,并且如果你使用的是系统自带的 python ,可能需要在命令前加上 sudo。

安装完成之后默认是不会创建配置文件的,因此再手动创建配置文件 /usr/local/etc/supervisord.conf,我的配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
[unix_http_server]
file=/tmp/supervisor.sock ; path to your socket file

[supervisord]
logfile=/usr/local/var/log/supervisord/supervisord.log ; supervisord log file
logfile_maxbytes=50MB ; maximum size of logfile before rotation
logfile_backups=10 ; number of backed up logfiles
loglevel=error ; info, debug, warn, trace
pidfile=/usr/local/var/run/supervisord.pid ; pidfile location
nodaemon=false ; run supervisord as a daemon
minfds=1024 ; number of startup file descriptors
minprocs=200 ; number of process descriptors
user=root ; default user
childlogdir=/usr/local/var/log/supervisord/ ; where child log files will live

[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

[supervisorctl]
serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket

[program:mongod]
command=/usr/local/bin/mongod --config /usr/local/etc/mongod.conf
autostart=false
autorestart=true

[program:redis]
command=/usr/local/bin/redis-server /usr/local/etc/redis.conf
autostart=false
autorestart=true

[program:nginx]
command=/usr/local/bin/nginx -c /usr/local/etc/nginx/nginx.conf
autostart=false
autorestart=true

这里我的这几个服务都没有设置为自动启动,如有需要可自行将 autostart 设置为 true。

最后执行 supervisord 命令启动 supervisor 服务。之后就可以通过 supervisorctl 命令来进行服务管理了。

Riot.js之初体验

Riot(http://riotjs.com/)按照官方的介绍,它是一个类似于 React 的微型框架。
压缩之后的文件只有差不多 15K 的大小,相比其他基本上都是上百K大小的框架来说确实是很微型的。

同时它的官方还给出了与 React 和 Polymer 的对比,各位感兴趣可以看看: http://riotjs.com/compare/

下面通过一个例子来体验一下。

先下载 Riot 库文件: https://raw.githubusercontent.com/riot/riot/master/riot+compiler.min.js
然后新建一个文件 index.html,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Tabs exampe</title>
<style type="text/css" media="screen">
.tabContent__item{
display:none;
}
.tabContent__item.is-active{
display:block;
}
</style>
</head>
<body>
<riot-tabs></riot-tabs>

<script src="tabs.tag" type="riot/tag"></script>
<script src="riot+compiler.min.js"></script>
<script type="text/javascript" charset="utf-8">
riot.mount('riot-tabs');
</script>
</body>
</html>

接着再创建文件 tabs.tag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<riot-tabs>
<h2>Tabs</h2>
<ul>
<li each={ tab, i in tabs } class="tabItem { is-active: parent.isActiveTab(tab.ref) }" onclick={ parent.toggleTab }>{tab.title}</li>
</ul>
<div class="tabContent">
<div each={ tab, i in tabs } class="tabContent__item { is-active: parent.isActiveTab(tab.ref) }">{tab.content}</div>
</div>

this.tabs = [
{ title: 'Tab 1', ref: 'tab1', content: "(1) Lorem ipsum dolor" },
{ title: 'Tab 2', ref: 'tab2', content: "(2) Lorem ipsum dolor" },
{ title: 'Tab 3', ref: 'tab3', content: "(3) Lorem ipsum dolor" }
]

this.activeTab = 'tab1'

isActiveTab(tab) {
return this.activeTab === tab
}

toggleTab(e) {
this.activeTab = e.item.tab.ref
return true
}
</riot-tabs>

这个是 javascript 与 html 的混合。当然还可以使用纯 js 的写法,将 tabs.tag 改为 tabs.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
riot.tag('riot-tabs', '<h2>Tabs</h2> <ul> <li each="{ tab, i in tabs }" class="tabItem { is-active: parent.isActiveTab(tab.ref) }" onclick="{ parent.toggleTab }">{tab.title}</li> </ul> <div class="tabContent"> <div each="{ tab, i in tabs }" class="tabContent__item { is-active: parent.isActiveTab(tab.ref) }">{tab.content}</div> </div>', function(opts) {

this.tabs = [
{ title: 'Tab 1', ref: 'tab1', content: "(1) Lorem ipsum dolor" },
{ title: 'Tab 2', ref: 'tab2', content: "(2) Lorem ipsum dolor" },
{ title: 'Tab 3', ref: 'tab3', content: "(3) Lorem ipsum dolor" }
];

this.activeTab = 'tab1';

this.isActiveTab = function(tab) {
return this.activeTab === tab;
}.bind(this);

this.toggleTab = function(e) {
this.activeTab = e.item.tab.ref;
return true;
}.bind(this);

});

同时将 index.html 中的 <script src="tabs.tag" type="riot/tag"></script> 修改为 <script src="tabs.js" type="riot/tag"></script>

好了,现在打开浏览器看下效果吧。

体验了一下,感觉它比 angular 之类的框架的学习成本要低。

AngularJS学习笔记5——路由

angularjs 的 ngRoute 模块提供了前端路由的功能。
在 angularjs 1.3 中 route 模块被单独提取了出来,要使用其功能需要将引用进来。

1
2
<script src="/lib/angular-1.3.15.min.js"></script>
<script src="/lib/angular-route-1.3.15.min.js"></script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var routeApp = angular.module('routeApp', ['ngRoute']);
routeApp.config(['$routeProvider',function ($routeProvider) {
$routeProvider
.when('/list', {
templateUrl: 'list.html',
controller: 'RouteListCtl'
})
.when('/list/:id', {
templateUrl: 'detail.html',
controller: 'RouteDetailCtl'
})
.otherwise({
redirectTo: '/list'
});
}]);

这里定义了两条路由规则,对应的视图模板分别为 list.html 和 detail.html

视图模板有三种定义方式:
方法1:编写在单独的文件中,然后 angularjs 通过 ajax 获取其内容;
方法2:直接在页面中使用 script 标签定义,如: <script type="text/ng-template" id="list.html"> .... </script>
方法3:使用 $templateCache 进行定义,如:

1
2
3
routeApp.run(function($templateCache) {
$templateCache.put('list.html', '...');
});

AngularJS学习笔记4——依赖注入

依赖注入

之前有遇到过 angularjs 的代码压缩之后就报错了,通过查文档得知是因为 angularjs 是通过变量名来查找相应服务的。
而 js 代码压缩之后变量名就变化了,从而无法找到相应的服务而报错。因此就需要指明依赖服务。

1
function SomeController($scope, $http) { ... }

例如以上代码就是定义了 SomeController 这个一个控制器,并依赖 $scope$http 两个服务。
但是当该代码压缩之后,这两个参数名都变化了。因此还需要使用以下代码来指明所需的依赖。

1
SomeController.$inject = ['$scope', '$http'];

或者另一个写法:

1
2
3
4
angular.module('app', [])
.controller('SomeController', ['$scope', '$http', function($scope, $http){

}]);

这样就可以保证我们的代码压缩之后也能正常运行了。

自定义服务

上面的 $http 服务为我们提供了 ajax 的功能。

需要自定义服务的可以使用 module.factory 方法或者 module.service 方法进行注册服务。
这两个方法稍微有些不同,一般我习惯使用 module.factory
然后在需要使用自定义服务的地方,将其添加到依赖列表中即可。

AngularJS学习笔记3——表单

双向绑定

使用 ngModel 指令将输入框与 model 进行绑定,如: <input type="text" ng-model="text" name="text" />
当输入框或者 model 的值其中一方改变都会影响另一方。

表单验证

之前表单验证都是用的 jquery-validation 这个插件,这需要编写大量的 js 代码用于逻辑控制,
现在改用 angularjs 之后方便了许多,不过还是要编写大量的视图代码。

一个简单的例子: https://github.com/wusuopu/angularjs-practice/blob/master/static/form/validate.html

1
2
3
4
5
6
7
8
9
<form action="#" name="user_form" ng-submit="submitForm(user_form.$valid, $event)" novalidate>

<div class="form-group" ng-class="{'has-error' : user_form.name.$invalid && user_form.name.$dirty, 'has-success' : user_form.name.$valid}">
<label>Name</label>
<input type="text" name="name" class="form-control" ng-model="formData.name" required>
<p ng-show="user_form.name.$invalid && user_form.name.$dirty" class="help-block">You name is required.</p>
</div>
.....
</form>

首先为 form 表单设置 name 属性,并设置 novalidate 属性禁止浏览器使用 HTML5 自带的验证功能。
然后对于需要验证的字段添加相应的验证属性,angularjs 内建的验证规则如下:

  • email
  • max
  • maxlength
  • min
  • minlength
  • number
  • pattern
  • required
  • url
  • date
  • datetimelocal
  • time
  • week
  • month

AngularJS学习笔记2——模板

ngRepeat

对于一些需要循环遍历的对象可以使用 ngRepeat 指令。 如:

1
2
3
4
5
6
7
8
9
10
11
<li ng-repeat="msg in messages">{{$index}} {{msg}}</li>

// 对于 messages 数组进行遍历
$scope.messages = [
"第一条消息",
"第二条消息",
"第三条消息",
"第四条消息",
"第五条消息"
];

ngSwitch

使用 ngSwitch 指令在不同条件下显示不同内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div ng-controller="ExampleController">
<select ng-model="selection" ng-options="item for item in items">
</select>
<code>selection={{selection}}</code>
<hr/>
<div class="animate-switch-container"
ng-switch="selection">
<div class="animate-switch" ng-switch-when="settings">Settings Div</div>
<div class="animate-switch" ng-switch-when="home">Home Span</div>
<div class="animate-switch" ng-switch-default>default</div>
</div>
</div>

<script type="text/javascript">
angular.module('myApp', []).controller('ExampleController', ['$scope', function($scope) {
$scope.items = ['settings', 'home', 'other'];
$scope.selection = $scope.items[0];
}]);
</script>

Filter

使用 filter 指令过滤需要的内容,基本形式: {{ expression | filter }}
如果有用过其他模板引擎的话(如:Jinja)应该很好理解的。

angularjs 提供了一些内置的过滤器:date, json, lowercase, uppercase 等。

ngIf

基本形式: <ANY ng-if="expression"> ... </ANY>
expression 的值为真时才会输出该标签。

AngularJS学习笔记1——基础入门

angularjs 是一款前端的 MVVM 框架,目前 2.0 好像也快要发布了。
angularjs 2.0 的理念又变为了 Web 组件,与 1.x 不兼容。
于是这里就把之前 angularjs 1.x 的学习笔记整理一下,作为以后回顾吧。

相关的事例代码可以从 https://github.com/wusuopu/angularjs-practice 获取。

简单事例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!doctype html>
<html lang="en" ng-app>
<head>
<meta charset="utf-8">
<title>基本例子</title>
<script src="/lib/angular-1.0.4.min.js"></script>
</head>
<body>
<p>1 + 2 = {{ 1 + 2 }}</p>
<div ng-controller="DemoController">
<p>{{content}}</p>
</div>
</body>
<script type="text/javascript" charset="utf-8">
function DemoController($scope){
$scope.content = "简单的控制器例子";
}
</script>
</html>

以上是一个最简单的例子。

1
<html lang="en" ng-app>

html 的 ng-app 属性表明在该页面中启用 angularjs。

1
<p>1 + 2 = {{ 1 + 2 }}</p>

在页面加载完成之后 angularjs 会解析大括号内的表达式,并进行渲染。

1
2
3
<div ng-controller="DemoController">
<p>{{content}}</p>
</div>

ng-controller 表示在该标签下定义一个控制器。一个控制器就是一个普通的 js 函数。

1
2
3
4
5
<script type="text/javascript" charset="utf-8">
function DemoController($scope){
$scope.content = "简单的控制器例子";
}
</script>

这里定义 DemoController 控制器函数,并传入一个 $scope 参数,它是用于表示该控制器的上下文。

unicorn是如何与nginx通讯的——介绍ruby中的unix socket

Ruby 应用服务典型地是与一个 web 服务一同使用的,如 nginx。当用户请求你的 Rails 应用中的页面时,nginx 将请求指派给应用服务。
然而这个过程是如何完成的呢?nginx 与 unicorn 是如何通讯的呢?

最有效的一种选择是使用 unix 套接字(sockets)。让我们来看看它们是如何工作的!
在这篇文章中我们将从一个基本的套接字(sockets)开始,最后将创建一个使用 nginx 代理的简单应用服务。

socket

套接字(sockets)允许进程之间通过对一个文件读或者写进行相互通讯。
在这个例子中 Unicorn 创建 socket 并监听它的连接。然后 Nginx 就可以连接到这个 socket 并与 Unicorn 通讯了。

什么是 unix socket?

Unix socket 使得一个进程通过类似文件的方式与另一个进程进行通讯。它是 IPC(Interprocess Communication) 的一种。

要使得可以通过 socket 访问进程,首先需要创建一个 socket 并作为一个文件保存在磁盘中。
然后监听这个 socket 的连入连接。当接收到一个连接时,就可以使用标准 IO 方法进行读写数据。

Ruby 通过以下一组类提供了 unix socket 所需的所有内容:

  • UNIXServer - 创建 socket 并保存到磁盘中,并且让你可以监听新连接。
  • UNIXSocket - 打开已存在的套接字(sockets)。

注意:还存在着其它类型的 socket,最突出的是 TCP socket。不过这篇文章只处理 unix socket。那么它们之间的区别是什么呢?unix socket 具有文件名。

最简单的 Socket

我们接下来看两个小程序。

第一个是服务端,它创建一个 UnixServer 实例,然后使用 server.accept 等待连接。当收到连接后则相互问候。

需要说明一下,acceptreadline 方法都会阻塞程序的执行,直到收到内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
require "socket"

server = UNIXServer.new('/tmp/simple.sock')

puts "==== Waiting for connection"
socket = server.accept

puts "==== Got Request:"
puts socket.readline

puts "==== Sending Response"
socket.write("I read you loud and clear, good buddy!")

socket.close

这里我们有了服务端,现在还需要客户端。

在下面这个例子中,我们打开由服务端创建的 socket,然后使用普通的 IO 方法进行发送和接收问候。

1
2
3
4
5
6
7
8
9
10
11
require "socket"

socket = UNIXSocket.new('/tmp/simple.sock')

puts "==== Sending"
socket.write("Hello server, can you hear me?\n")

puts "==== Getting Response"
puts socket.readline

socket.close

演示一下程序,先运行服务端,然后再运行客户端。你可以看到以下结果:

simple_ruby_socket_example
简单的 Unix socket 服务端/客户端交互的例子。左边是客户端,右边是服务端。

与 nginx 接合

现在我们知道如何创建一个 unix socket 的服务端了,我们可以很容易地与 nginx 接合。

不相信我?让我们来做一个快速的概念验证吧。我修改上面的代码使其输出从 socket 接收到的所有内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
require "socket"

# Create the socket and "save it" to the file system
server = UNIXServer.new('/tmp/socktest.sock')

# Wait until for a connection (by nginx)
socket = server.accept

# Read everything from the socket
while line = socket.readline
puts line.inspect
end

socket.close

现在如果我修改 nginx 配置,将请求转发到 /tmp/socktest.sock socket 上。
我就能看到 nginx 发送来的数据了。(别担心,我们稍后会讨论它的配置的)

当我发起一个 web 请求时,nginx 将如下数据发送到我的服务端上:

request_http

太酷了!这就是一个包含了额外头信息的 HTTP 请求。现在我们准备来构建一个真正的应用服务。
但是,首先让我们讨论一个 nginx 的配置吧。

安装配置 Nginx

如果你还没有安装 nginx 的话,请先安装。对于 OSX 可以 homebrew 简单完成:

1
brew install nginx

现在我们配置 nginx 将 localhost:2048 的请求通过名为 /tmp/socktest.sock 的 socket 转发到上游服务端。
名字可以是任意的,它仅需要与我们 web 服务的 socket 名字匹配即可。

你可以将其保存至 /tmp/nginx.conf 并通过命令 nginx -c /tmp/nginx.conf 运行 nginx。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# Run nginx as a normal console program, not as a daemon
daemon off;

# Log errors to stdout
error_log /dev/stdout info;

events {} # Boilerplate

http {

# Print the access log to stdout
access_log /dev/stdout;

# Tell nginx that there's an external server called @app living at our socket
upstream app {
server unix:/tmp/socktest.sock fail_timeout=0;
}

server {

# Accept connections on localhost:2048
listen 2048;
server_name localhost;

# Application root
root /tmp;

# If a path doesn't exist on disk, forward the request to @app
try_files $uri/index.html $uri @app;

# Set some configuration options on requests forwarded to @app
location @app {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://app;
}

}
}

以非后台模式运行 nginx。当你运行 nginx 时它应该看起来像如下这样:

nginx_non_daemon
Nginx 以非后台模式运行

自定义应用服务

既然我们已经知道了如何将 nginx 与我们程序进行连接,那么我们就来构建一个简单的应用服务。
当 nginx 将请求转发到我们的 socket 时,它是一个标准的 HTTP 请求。
经过一些处理后我可以决定 socket 是否会返回一个有效的 HTTP 响应,它会在浏览器中显示。

下面这个应用接受任何请求并显示时间戳。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
require "socket"

# Connection creates the socket and accepts new connections
class Connection

attr_accessor :path

def initialize(path:)
@path = path
File.unlink(path) if File.exists?(path)
end

def server
@server ||= UNIXServer.new(@path)
end

def on_request
socket = server.accept
yield(socket)
socket.close
end
end


# AppServer logs incoming requests and renders a view in response
class AppServer

attr_reader :connection
attr_reader :view

def initialize(connection:, view:)
@connection = connection
@view = view
end

def run
while true
connection.on_request do |socket|
while (line = socket.readline) != "\r\n"
puts line
end
socket.write(view.render)
end
end
end

end

# TimeView simply provides the HTTP response
class TimeView
def render
%[HTTP/1.1 200 OK


The current timestamp is: #{ Time.now.to_i }

]
end
end


AppServer.new(connection: Connection.new(path: '/tmp/socktest.sock'), view: TimeView.new).run

现在运行 nginx 和脚本,然后访问 localhost:2048。请求会发送到我的应用上,然后响应被浏览器渲染。太酷了!

appserver
HTTP 请求信息由我们的应用服务输出到 STDOUT

以下就是我们的劳动成果。

timestamp
浏览器中显示服务端返回的时间戳

原文地址: http://blog.honeybadger.io/how-unicorn-talks-to-nginx-an-introduction-to-unix-sockets-in-ruby/

Ruby的内存陷阱

Ruby有一套自动的内存管理机制。这在大多数情况下是不错的,但是有时它却是个麻烦。

Ruby的内存管理既简洁又笨重。它将对象(名为 RVALUE)存储在大约有16KB大小的堆中。
从底层上,RVALUE 是一个 C 的结构体,它包含了一个共同体表示不同的标准ruby对象。

因此在堆中存储着大小不超过40字节的 RVALUE 对象,如 StringArrayHash等。
这意味着小的对象在堆中很合适,但是一旦它们达到到阈值,那么就需要在Ruby的堆之外再分配一片额外的内存。

这块额外的内存空间是灵活的。一旦对象被垃圾回收了它就会被释放掉。但是堆本身的内存是不会被释放给操作系统的。

让我们来看一个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def report
puts 'Memory ' + `ps ax -o pid,rss | grep -E "^[[:space:]]*#{$$}"`
.strip.split.map(&:to_i)[1].to_s + 'KB'
end

report
big_var = " " * 10_000_000
report
big_var = nil
report
ObjectSpace.garbage_collect
sleep 1
report

# ⇒ Memory 11788KB
# ⇒ Memory 65188KB
# ⇒ Memory 65188KB
# ⇒ Memory 11788KB

这里我们分配了大量的内存,使用完后又释放给操作系统。这一切看起来似乎没有问题。现在让我们稍微修改一下代码:

1
2
-  big_var = " " * 10_000_000
+ big_var = 1_000_000.times.map(&:to_s)

这只是一个简单的修改,不是吗。但是结果:

1
2
3
4
# ⇒ Memory 11788KB
# ⇒ Memory 65188KB
# ⇒ Memory 65188KB
# ⇒ Memory 57448KB

怎么回事?内存没有释放归还给操作系统。这是因为数组中的每个元素符合 RVALUE 的大小并存储在ruby的堆中。

在大多情况下这是正常的。现在ruby堆中多了许多空的位置,再次运行代码将不会再消耗额外的内存了。
每次我们处理 big_var 和一些空的堆时, GC[:heap_used]的值果然减小了。
对于这些操作Ruby是早有准备,注意这里是Ruby而不是操作系统。

因此,对于创建大量的符合40个字节的临时变量就要注意了:

1
2
big_var = " " * 10_000_000
big_var.gsub(/\s/) { |c| '-' }

结果同样是Ruby的内存疯狂增长,并且这部分内存在程序运行期间是不会归还给操作系统的:

1
2
3
4
# ⇒ Memory 10156KB
# ⇒ Memory 13788KB
# ⇒ Memory 13788KB
# ⇒ Memory 12808KB

这个问题不是太重要,稍微注意一下即可。

原文地址:http://rocket-science.ru/hacking/2013/12/17/ruby-memory-pitfalls/