swee 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2 @@
1
+ class OptionsError < ArgumentError
2
+ end
@@ -0,0 +1,29 @@
1
+ require "erb"
2
+ # require "cgi"
3
+
4
+ module Swee
5
+
6
+ module Helper
7
+ def raw text
8
+ text.html_safe
9
+ end
10
+
11
+ def image_tag
12
+
13
+ end
14
+
15
+ def form_for
16
+
17
+ end
18
+
19
+ def tag(name, options = nil, open = false, escape = true)
20
+
21
+ "<#{name}#{tag_options(options, escape) if options}#{open ? ">" : " />"}".html_safe
22
+ end
23
+
24
+ def parse_tag_options options
25
+
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,58 @@
1
+ module Swee
2
+ module Installer
3
+ module_function
4
+ def run proj_name
5
+
6
+ # 打印安装信息
7
+ puts "--------------- Swee 安装 -----------------"
8
+ proj_dir = "#{Dir.pwd}/#{proj_name}"
9
+
10
+ # 创建项目目录
11
+ if Dir.exist?(proj_dir)
12
+ puts "当前路径已存在#{proj_name}目录"
13
+ return
14
+ end
15
+ puts "#{proj_name}目录创建成功"
16
+ Dir.mkdir(proj_dir)
17
+
18
+ # 安装目录
19
+ app_path = proj_dir
20
+ ["public","controllers","logs","models","tmp","views","views/home"].each do |dir|
21
+ _dir = app_path + "/#{dir}"
22
+ if Dir.exist?(_dir)
23
+ puts "#{_dir}目录已存在,略过..."
24
+ else
25
+ Dir.mkdir(_dir)
26
+ puts "#{_dir}目录成功创建"
27
+ end
28
+ end
29
+
30
+ # 安装 文件
31
+ ["config.rb","routes.rb","public/404.html","controllers/HomeController.rb", "views/home/index.erb"].each do |_file|
32
+ file = app_path + "/#{_file}"
33
+
34
+ if File.exist?(file)
35
+ puts "#{file} 已经安装过了"
36
+ else
37
+ f = File.open(file,"w")
38
+ config_file = File.expand_path("../template/#{_file}",File.dirname(__FILE__))
39
+ f.write(File.read(config_file))
40
+ f.close
41
+ puts "#{file} 已经安装"
42
+ end
43
+ end
44
+
45
+ # 打印结束信息
46
+ puts "恭喜,全部安装完成"
47
+ puts "您可以先输入: swee s 来启动服务器"
48
+ puts "请打开config.rb 和 routes.rb 配置所需的参数"
49
+ puts "更多参数请输入: swee --help"
50
+ puts ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
51
+ puts "我们先启动一个欢迎页面"
52
+ puts "浏览器输入 http://localhost:3000 来访问欢迎页面吧"
53
+ puts "按 Ctrl + C 关闭服务"
54
+ puts ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
55
+ `cd #{proj_name} && swee s`
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,92 @@
1
+ module Swee
2
+ module Lodder
3
+ @@mtime_files_cache = {}
4
+
5
+ CACHE_FILE_MTIME_DIR = ["models","controllers"]
6
+ EXTENSION_NAMES = [".rb",".erb",".haml",".slim","ymal"]
7
+
8
+ module_function
9
+
10
+ def cache_file_mtime
11
+ CACHE_FILE_MTIME_DIR.each { |d| Lodder.search_app_file(d) }
12
+ end
13
+
14
+ def mtime_files
15
+ @@mtime_files_cache
16
+ end
17
+
18
+ # 递归寻找目录下所有文件
19
+ # 保存为如下结构(用于 代码修改 reload)
20
+ # filename => mtime
21
+ def search_app_file dir
22
+ app_path = ENV["app_path"]
23
+ Dir.glob("#{app_path}/#{dir}/*") do |file|
24
+ if EXTENSION_NAMES.include? File.extname(file).downcase
25
+ @@mtime_files_cache[file] = File.mtime(file)
26
+ else
27
+ if File.directory?(file)
28
+ search_app_file(dir + "/" + file.split("/").last)
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ def base_require
35
+ require_relative './support'
36
+ require_relative './config'
37
+ # require_relative './application'
38
+
39
+ # patches
40
+ require_relative './patches/logger.rb'
41
+
42
+ # app
43
+ require_relative './routes'
44
+ require_relative './helper'
45
+ require_relative './controller_filter'
46
+ require_relative './controller'
47
+ require_relative './view'
48
+ require_relative './app_executor'
49
+
50
+ # middlewaves
51
+ require_relative './middlewaves/content_length'
52
+ require_relative './middlewaves/common_logger'
53
+ require_relative './middlewaves/reloader'
54
+
55
+ # thin
56
+ require_relative './thin/headers'
57
+ require_relative './thin/request'
58
+ require_relative './thin/response'
59
+
60
+ # server
61
+ require_relative './swee_logger'
62
+ require_relative './daemonize'
63
+ require_relative './connection'
64
+ require_relative './server'
65
+ require_relative './exception'
66
+
67
+ end
68
+
69
+ # 用户配置
70
+ def app_require
71
+ # require_relative './application'
72
+ begin
73
+ require File.expand_path('config', ENV["app_path"])
74
+ rescue LoadError
75
+ raise "未找到config.rb"
76
+ end
77
+ end
78
+
79
+ # 条件读取
80
+ def conditional_require
81
+ # 存在 AR 读取
82
+ unless Gem.find_files("active_record").empty?
83
+ require 'active_record'
84
+ end
85
+ end
86
+
87
+ def all
88
+ conditional_require
89
+ base_require
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,29 @@
1
+ module Rack
2
+ class CommonLogger
3
+ def initialize(app, logger=nil)
4
+ @app = app
5
+ @logger = logger
6
+ end
7
+
8
+ private
9
+
10
+ def log(env, status, header, began_at)
11
+ now = Time.now
12
+ length = extract_content_length(header)
13
+
14
+ msg = FORMAT % [
15
+ env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-",
16
+ env["REMOTE_USER"] || "-",
17
+ now.strftime("%d/%b/%Y:%H:%M:%S %z"),
18
+ env['REQUEST_METHOD'],
19
+ env['PATH_INFO'],
20
+ env['QUERY_STRING'].empty? ? "" : "?"+env['QUERY_STRING'],
21
+ env["SERVER_PROTOCOL"],
22
+ status.to_s[0..3],
23
+ length,
24
+ now - began_at ]
25
+
26
+ @logger << msg
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,21 @@
1
+ module Swee
2
+
3
+ class ContentLength
4
+ def initialize(app)
5
+ @app = app
6
+ end
7
+
8
+ def call(env)
9
+ status, headers, body = @app.call(env)
10
+ headers.merge!( { "Content-Length" => calc_length(body) } )
11
+ [status, headers, body]
12
+ end
13
+
14
+ private
15
+ def calc_length(body)
16
+ length = 0
17
+ body.each { |part| length += part.bytesize }
18
+ length
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,24 @@
1
+ module Swee
2
+
3
+ class Reloader
4
+ def initialize(app,logger)
5
+ @app = app
6
+ @logger = logger
7
+ app_path = ENV["app_path"]
8
+ end
9
+
10
+ def call(env)
11
+ # 快速遍历 文件 mtime cache表
12
+ # mtime不一致则重新 load
13
+ Lodder.mtime_files.each_pair do |file,omtime|
14
+ mtime = File.mtime(file)
15
+ if mtime != omtime
16
+ load file
17
+ Lodder.mtime_files[file] = mtime
18
+ @logger << "#{file}文件已重载!"
19
+ end
20
+ end
21
+ return @app.call(env)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,15 @@
1
+ require "logger" if !defined? Logger
2
+
3
+ class Logger
4
+
5
+ # 判断当前日志是否输出到stdout
6
+ def io?
7
+ instance_variable_get(:@logdev).dev === STDOUT
8
+ end
9
+
10
+
11
+ # 判断当前日志输出到logfile
12
+ def file?
13
+ instance_variable_get(:@logdev).dev.is_a?(File)
14
+ end
15
+ end
@@ -0,0 +1,46 @@
1
+ module Swee
2
+ class Routes
3
+ @@tables = {}
4
+ class RouteStruct
5
+ attr_reader :controller, :action, :request_methods
6
+ def initialize c,a,m
7
+ @controller,@action,@request_methods = c,a,m
8
+ end
9
+
10
+ def controller_name
11
+ "#{controller[0].upcase+controller[1..controller.size-1]}Controller"
12
+ end
13
+
14
+ def create_controller_instance
15
+ eval "#{controller_name}.new"
16
+ end
17
+ end
18
+ class << self
19
+
20
+ def get *args
21
+ self._parse "get",*args
22
+ end
23
+
24
+ def post *args
25
+ self._parse "post",*args
26
+ end
27
+
28
+ def match *args
29
+ self._parse "match",*args
30
+ end
31
+
32
+ def tables
33
+ @@tables
34
+ end
35
+
36
+ def _parse _m,*args
37
+ _path_info = args[0].to_s
38
+ if args[1] =~ /^(.*)#(.*)$/
39
+ @@tables[_path_info] = RouteStruct.new $1,$2, _m == "match" ? args[2][:via].map(&:to_sym) : [_m.to_sym]
40
+ else
41
+ raise "routes error!"
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,298 @@
1
+ module Swee
2
+ class Server
3
+ include Daemonize
4
+
5
+ attr_reader :code_reload
6
+
7
+ def initialize
8
+ @config = Swee.config
9
+ @options = @config.server
10
+
11
+ @signature = nil
12
+
13
+ @handle_request_mode = @options[:handle_request_mode]
14
+
15
+ @restart_mode = @options[:restart_mode]
16
+ @pid_file = File.expand_path(@options[:pid_file],ENV["app_path"])
17
+ @touch_file = File.expand_path(@options[:touch_file],ENV["app_path"])
18
+
19
+ @logger = nil
20
+ @log_file_options = @options[:log_file]
21
+ @log_file = File.expand_path(@log_file_options.first,ENV["app_path"])
22
+ @log_file_options[0] = @log_file
23
+
24
+ @logger_level = @options[:logger_level]
25
+
26
+ @code_reload = @options[:code_reload]
27
+
28
+ # 最大连接数 和 稳定连接处
29
+ @maximum_connections = @options[:max_connections]
30
+ @maximum_persistent_connections = @options[:max_connections]
31
+
32
+ # 监听端口
33
+ @listen = @options[:listen]
34
+
35
+ # 实际连接 __id__ => connection
36
+ @connections = {}
37
+
38
+ # 超时时间
39
+ @timeout = 30
40
+
41
+ # 活动连接数
42
+ @persistent_connection_count = 0
43
+
44
+ # touch 模式重启时间
45
+ @restart_time = nil
46
+
47
+ # 信号队列 暂时只处理 :USR2
48
+ @signal_queue = []
49
+
50
+ # 守护进程
51
+ @daemonize = @options[:run_background]
52
+
53
+ # todo: 性能监控
54
+ # @performance_monitoring = @options[:performance_monitoring]
55
+ end
56
+
57
+ # 获取 Logger STDOUT 和 File
58
+ def logger
59
+ @logger
60
+ end
61
+
62
+ # 删除pid文件 用于重启 和 exit
63
+ def remove_pid_file
64
+ File.delete(@pid_file) if @pid_file && File.exists?(@pid_file)
65
+ end
66
+
67
+ # 读取pid文件 用于 USR2 信号获得后和Process.pid 比对
68
+ def read_pid_file
69
+ File.read(@pid_file)
70
+ end
71
+
72
+ # 写入Process.pid到 pid 文件
73
+ def write_pid_file
74
+ open(@pid_file,"w") { |f| f.write(Process.pid) }
75
+ File.chmod(0644, @pid_file)
76
+ end
77
+
78
+ # 是否运行标志
79
+ def running?
80
+ @running
81
+ end
82
+
83
+ # 强制停止
84
+ def stop!
85
+ @running = false
86
+ disconnect
87
+ EventMachine.stop
88
+ @connections.each_value { |connection| connection.close_connection }
89
+ end
90
+
91
+ # 放入 注册的 exit_at 函数中
92
+ # 重启 停止服务 -> 删除pid文件 -> 退出进程
93
+ #
94
+ def restart
95
+ stop!
96
+ remove_pid_file
97
+ exit
98
+ end
99
+
100
+ # 处理日志 stdout 和 file 两种
101
+ def handle_logger
102
+ # 创建日志文件
103
+ if !File.exist?(@log_file)
104
+ FileUtils.mkdir_p File.dirname(@log_file)
105
+ end
106
+ logs = []
107
+ logs << Logger.new(*@log_file_options)
108
+ unless @daemonize
109
+ logs << Logger.new(STDOUT)
110
+ end
111
+
112
+ # todo: 日志等级, datetime, 格式配置
113
+ # logs.each do |_logger|
114
+ # _logger.level = @logger_level
115
+
116
+ # _logger.datetime_format = '%Y-%m-%d %H:%M:%S'
117
+ # _logger.formatter = proc do |severity, datetime, progname, msg|
118
+ # "Started GET #{msg} at #{datetime}"
119
+ # end
120
+ # end
121
+
122
+ @logger = SweeLogger.new
123
+
124
+ logs.each { |_logger| @logger.addlog _logger }
125
+ end
126
+
127
+ # 处理重启
128
+ def handle_restart
129
+ send "handle_" + @restart_mode.to_s + "_restart"
130
+ end
131
+
132
+ # 处理touch重启方式
133
+ def handle_touch_restart
134
+ @restart_time = File.mtime(@touch_file)
135
+ end
136
+
137
+ # 处理pid重启方式
138
+ def handle_pid_restart
139
+ write_pid_file
140
+ end
141
+
142
+ # 判断是否为 touch 重启方式
143
+ def touch_mode?
144
+ @restart_mode == :touch
145
+ end
146
+
147
+ # 判断是否为 USR2 pid 重启方式
148
+ def pid_mode?
149
+ @restart_mode == :pid
150
+ end
151
+
152
+ # 判断 pid 文件是否为 当前进程ID
153
+ def current_pid? pid
154
+ read_pid_file == Process.pid.to_s
155
+ end
156
+
157
+ # 追踪重启 EM Timer 方式
158
+ # 每秒执行一次
159
+ # touch: 每秒读取touch文件 mtime
160
+ # pid: trap usr2 信号放入 信号队列, 然后每秒追踪队列变化获取信号
161
+ def trace_restart
162
+ if touch_mode?
163
+ EM.add_periodic_timer(1) {
164
+ mtime = File.mtime(@touch_file)
165
+ if mtime != @restart_time
166
+ @restart_time = mtime
167
+ puts "重启了"
168
+ restart
169
+ # 放弃 next_tick 原因:会在下次有请求时处理重启
170
+ # EM.next_tick { restart }
171
+ end
172
+ }
173
+ end
174
+
175
+ if pid_mode?
176
+ EM.add_periodic_timer(1) {
177
+ signal = @signal_queue.shift
178
+ case signal
179
+ when nil
180
+ when :QUIT # graceful shutdown
181
+ when :TERM, :INT # immediate shutdown
182
+ stop!
183
+ when :USR1 # rotate logs
184
+ when :USR2 # exec binary, stay alive in case something went wrong
185
+ _restart = current_pid?
186
+ when :WINCH
187
+ when :TTIN
188
+ when :TTOU
189
+ when :HUP
190
+ end
191
+ restart if _restart
192
+ }
193
+ trap(:USR2) {
194
+ # 收到 USR2信号加入 信号队列
195
+ @signal_queue << :USR2
196
+ }
197
+ end
198
+ end
199
+
200
+ # 处理守护进程(后台运行)
201
+ # 创建子进程
202
+ # 进程命名
203
+ # 注册守护进程重启
204
+ def handle_daemonize
205
+ # 先删除pid文件
206
+ remove_pid_file
207
+
208
+ # todo 关闭io 管道
209
+ # rd, wr = IO.pipe
210
+ # grandparent = $$
211
+
212
+ # 创建子进程
213
+ safefork && exit
214
+
215
+ # 设定进程名称
216
+ $0 = "swee"
217
+
218
+ # 重新创建pid文件
219
+ write_pid_file
220
+
221
+ # 注册守护进程重启
222
+ at_exit{
223
+ # 简单的用异常判断 系统退出,并非其他异常 获得重启
224
+ if $!.class == SystemExit
225
+ @logger << "Swee服务器重启!"
226
+ remove_pid_file
227
+ ::Swee::Engine.restart_server!
228
+ end
229
+ }
230
+ end
231
+
232
+ # 创建日志 和 pid 文件
233
+ def create_touch_and_pid_file
234
+ [@pid_file,@touch_file].each do |_file|
235
+ if !File.exist?(_file)
236
+ FileUtils.mkdir_p File.dirname(_file)
237
+ open(_file,"w")
238
+ File.chmod(0644, _file)
239
+ end
240
+ end
241
+ end
242
+
243
+ # 启动服务器
244
+ def run!
245
+ # 创建日志和pid文件
246
+ create_touch_and_pid_file
247
+ # 处理日志
248
+ handle_logger
249
+ # 处理重启
250
+ handle_restart
251
+ # EM默认配置
252
+ EventMachine.threadpool_size = 20
253
+ EventMachine.epoll
254
+ EventMachine.set_descriptor_table_size(@maximum_connections)
255
+
256
+ @logger << "正在启动swee服务器"
257
+
258
+ EventMachine::run {
259
+ @signature = connection
260
+ @running = true
261
+
262
+ # EM启动后处理后台守护进程运行
263
+ handle_daemonize if @daemonize
264
+
265
+ # 追踪重启文件
266
+ trace_restart
267
+
268
+ puts "swee 服务器已启动, 端口:#{@listen}"
269
+ }
270
+ end
271
+
272
+ # 连接
273
+ def connection
274
+ EventMachine::start_server "0.0.0.0", @listen, Connection do |_connection|
275
+ _connection.server = self
276
+
277
+ # 记录活动连接数
278
+ if @persistent_connection_count < @maximum_persistent_connections
279
+ @persistent_connection_count += 1
280
+ end
281
+ @connections[_connection.__id__] = _connection
282
+ end
283
+ end
284
+
285
+ # 断开
286
+ def disconnect
287
+ EventMachine.stop_server(@signature)
288
+ end
289
+
290
+ # 完成连接
291
+ def connection_finished(connection)
292
+ @persistent_connection_count -= 1
293
+ @connections.delete(connection.__id__)
294
+
295
+ # TODO: 停止或重启
296
+ end
297
+ end
298
+ end