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.
- checksums.yaml +7 -0
- data/README.md +212 -0
- data/Rakefile +4 -0
- data/bin/swee +38 -0
- data/doc/tmp.rb +77 -0
- data/lib/swee.rb +5 -0
- data/lib/swee/app_executor.rb +152 -0
- data/lib/swee/config.rb +118 -0
- data/lib/swee/connection.rb +48 -0
- data/lib/swee/controller.rb +158 -0
- data/lib/swee/controller_filter.rb +67 -0
- data/lib/swee/daemonize.rb +17 -0
- data/lib/swee/engine.rb +201 -0
- data/lib/swee/exception.rb +2 -0
- data/lib/swee/helper.rb +29 -0
- data/lib/swee/installer.rb +58 -0
- data/lib/swee/lodder.rb +92 -0
- data/lib/swee/middlewaves/common_logger.rb +29 -0
- data/lib/swee/middlewaves/content_length.rb +21 -0
- data/lib/swee/middlewaves/reloader.rb +24 -0
- data/lib/swee/patches/logger.rb +15 -0
- data/lib/swee/routes.rb +46 -0
- data/lib/swee/server.rb +298 -0
- data/lib/swee/support.rb +79 -0
- data/lib/swee/swee_logger.rb +59 -0
- data/lib/swee/thin/headers.rb +40 -0
- data/lib/swee/thin/request.rb +162 -0
- data/lib/swee/thin/response.rb +177 -0
- data/lib/swee/version.rb +3 -0
- data/lib/swee/view.rb +36 -0
- data/lib/template/config.rb +38 -0
- data/lib/template/controllers/HomeController.rb +11 -0
- data/lib/template/public/404.html +66 -0
- data/lib/template/routes.rb +11 -0
- data/lib/template/todo_config.rb +75 -0
- data/lib/template/views/home/index.erb +2 -0
- metadata +164 -0
data/lib/swee/support.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
class Object
|
2
|
+
def try(*a, &b)
|
3
|
+
try!(*a, &b) if a.empty? || respond_to?(a.first)
|
4
|
+
end
|
5
|
+
|
6
|
+
def try!(*a, &b)
|
7
|
+
if a.empty? && block_given?
|
8
|
+
if b.arity.zero?
|
9
|
+
instance_eval(&b)
|
10
|
+
else
|
11
|
+
yield self
|
12
|
+
end
|
13
|
+
else
|
14
|
+
public_send(*a, &b)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def blank?
|
19
|
+
respond_to?(:empty?) ? !!empty? : !self
|
20
|
+
end
|
21
|
+
def present?
|
22
|
+
!blank?
|
23
|
+
end
|
24
|
+
|
25
|
+
def presence
|
26
|
+
self if present?
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class NilClass
|
31
|
+
def try(*args)
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
|
35
|
+
def try!(*args)
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
|
39
|
+
def blank?
|
40
|
+
true
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class FalseClass
|
45
|
+
def blank?
|
46
|
+
true
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class TrueClass
|
51
|
+
def blank?
|
52
|
+
false
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class Array
|
57
|
+
alias_method :blank?, :empty?
|
58
|
+
end
|
59
|
+
|
60
|
+
class Hash
|
61
|
+
alias_method :blank?, :empty?
|
62
|
+
end
|
63
|
+
|
64
|
+
class String
|
65
|
+
BLANK_RE = /\A[[:space:]]*\z/
|
66
|
+
def blank?
|
67
|
+
BLANK_RE === self
|
68
|
+
end
|
69
|
+
|
70
|
+
def html_safe
|
71
|
+
self.gsub /[&"'><]/, { '&' => '&', '>' => '>', '<' => '<', '"' => '"', "'" => ''' }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class Numeric
|
76
|
+
def blank?
|
77
|
+
false
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require "fiber"
|
2
|
+
|
3
|
+
class Swee::SweeLogger
|
4
|
+
def initialize
|
5
|
+
@logs = Array.new
|
6
|
+
@msg = Array.new
|
7
|
+
init_fiber
|
8
|
+
end
|
9
|
+
|
10
|
+
def init_fiber
|
11
|
+
@fb = Fiber.new { loop_log }
|
12
|
+
end
|
13
|
+
|
14
|
+
def loop_log
|
15
|
+
loop do
|
16
|
+
each_log
|
17
|
+
Fiber.yield
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def each_log
|
22
|
+
while !@msg.empty?
|
23
|
+
_msg = @msg.shift
|
24
|
+
@logs.each { |_log| _log.debug _msg }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def get_binding
|
29
|
+
binding
|
30
|
+
end
|
31
|
+
|
32
|
+
def roll!
|
33
|
+
each_log
|
34
|
+
# Fiber 存在跨线程问题 暂时不用Fiber处理
|
35
|
+
# init_fiber if !@fb.alive?
|
36
|
+
# @fb.resume
|
37
|
+
end
|
38
|
+
|
39
|
+
def addlog log
|
40
|
+
@logs << log
|
41
|
+
end
|
42
|
+
|
43
|
+
def get_io
|
44
|
+
@logs.select { |log| log.io? }.first
|
45
|
+
end
|
46
|
+
|
47
|
+
def get_file
|
48
|
+
@logs.select { |log| log.file? }.first
|
49
|
+
end
|
50
|
+
|
51
|
+
def logs
|
52
|
+
@logs
|
53
|
+
end
|
54
|
+
|
55
|
+
def <<(msg)
|
56
|
+
@msg << msg
|
57
|
+
roll!
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Thin
|
2
|
+
# Store HTTP header name-value pairs direcly to a string
|
3
|
+
# and allow duplicated entries on some names.
|
4
|
+
class Headers
|
5
|
+
HEADER_FORMAT = "%s: %s\r\n".freeze
|
6
|
+
ALLOWED_DUPLICATES = %w(set-cookie set-cookie2 warning www-authenticate).freeze
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@sent = {}
|
10
|
+
@out = []
|
11
|
+
end
|
12
|
+
|
13
|
+
# Add <tt>key: value</tt> pair to the headers.
|
14
|
+
# Ignore if already sent and no duplicates are allowed
|
15
|
+
# for this +key+.
|
16
|
+
def []=(key, value)
|
17
|
+
downcase_key = key.downcase
|
18
|
+
if !@sent.has_key?(downcase_key) || ALLOWED_DUPLICATES.include?(downcase_key)
|
19
|
+
@sent[downcase_key] = true
|
20
|
+
value = case value
|
21
|
+
when Time
|
22
|
+
value.httpdate
|
23
|
+
when NilClass
|
24
|
+
return
|
25
|
+
else
|
26
|
+
value.to_s
|
27
|
+
end
|
28
|
+
@out << HEADER_FORMAT % [key, value]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def has_key?(key)
|
33
|
+
@sent[key.downcase]
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_s
|
37
|
+
@out.join
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
|
3
|
+
module Thin
|
4
|
+
# Raised when an incoming request is not valid
|
5
|
+
# and the server can not process it.
|
6
|
+
class InvalidRequest < IOError; end
|
7
|
+
|
8
|
+
# A request sent by the client to the server.
|
9
|
+
class Request
|
10
|
+
# Maximum request body size before it is moved out of memory
|
11
|
+
# and into a tempfile for reading.
|
12
|
+
MAX_BODY = 1024 * (80 + 32)
|
13
|
+
BODY_TMPFILE = 'thin-body'.freeze
|
14
|
+
MAX_HEADER = 1024 * (80 + 32)
|
15
|
+
|
16
|
+
INITIAL_BODY = ''
|
17
|
+
# Force external_encoding of request's body to ASCII_8BIT
|
18
|
+
INITIAL_BODY.encode!(Encoding::ASCII_8BIT) if INITIAL_BODY.respond_to?(:encode!)
|
19
|
+
|
20
|
+
# Freeze some HTTP header names & values
|
21
|
+
SERVER_SOFTWARE = 'SERVER_SOFTWARE'.freeze
|
22
|
+
SERVER_NAME = 'SERVER_NAME'.freeze
|
23
|
+
REQUEST_METHOD = 'REQUEST_METHOD'.freeze
|
24
|
+
LOCALHOST = 'localhost'.freeze
|
25
|
+
HTTP_VERSION = 'HTTP_VERSION'.freeze
|
26
|
+
HTTP_1_0 = 'HTTP/1.0'.freeze
|
27
|
+
REMOTE_ADDR = 'REMOTE_ADDR'.freeze
|
28
|
+
CONTENT_LENGTH = 'CONTENT_LENGTH'.freeze
|
29
|
+
CONNECTION = 'HTTP_CONNECTION'.freeze
|
30
|
+
KEEP_ALIVE_REGEXP = /\bkeep-alive\b/i.freeze
|
31
|
+
CLOSE_REGEXP = /\bclose\b/i.freeze
|
32
|
+
HEAD = 'HEAD'.freeze
|
33
|
+
|
34
|
+
# Freeze some Rack header names
|
35
|
+
RACK_INPUT = 'rack.input'.freeze
|
36
|
+
RACK_VERSION = 'rack.version'.freeze
|
37
|
+
RACK_ERRORS = 'rack.errors'.freeze
|
38
|
+
RACK_MULTITHREAD = 'rack.multithread'.freeze
|
39
|
+
RACK_MULTIPROCESS = 'rack.multiprocess'.freeze
|
40
|
+
RACK_RUN_ONCE = 'rack.run_once'.freeze
|
41
|
+
ASYNC_CALLBACK = 'async.callback'.freeze
|
42
|
+
ASYNC_CLOSE = 'async.close'.freeze
|
43
|
+
|
44
|
+
# CGI-like request environment variables
|
45
|
+
attr_reader :env
|
46
|
+
|
47
|
+
# Unparsed data of the request
|
48
|
+
attr_reader :data
|
49
|
+
|
50
|
+
# Request body
|
51
|
+
attr_reader :body
|
52
|
+
|
53
|
+
def initialize
|
54
|
+
@parser = Thin::HttpParser.new
|
55
|
+
@data = ''
|
56
|
+
@nparsed = 0
|
57
|
+
@body = StringIO.new(INITIAL_BODY.dup)
|
58
|
+
@env = {
|
59
|
+
# SERVER_SOFTWARE => SERVER,
|
60
|
+
SERVER_NAME => LOCALHOST,
|
61
|
+
|
62
|
+
# Rack stuff
|
63
|
+
RACK_INPUT => @body,
|
64
|
+
|
65
|
+
# RACK_VERSION => VERSION::RACK,
|
66
|
+
RACK_ERRORS => STDERR,
|
67
|
+
|
68
|
+
RACK_MULTITHREAD => false,
|
69
|
+
RACK_MULTIPROCESS => false,
|
70
|
+
RACK_RUN_ONCE => false
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
# Parse a chunk of data into the request environment
|
75
|
+
# Raises an +InvalidRequest+ if invalid.
|
76
|
+
# Returns +true+ if the parsing is complete.
|
77
|
+
def parse(data)
|
78
|
+
if @parser.finished? # Header finished, can only be some more body
|
79
|
+
@body << data
|
80
|
+
else # Parse more header using the super parser
|
81
|
+
@data << data
|
82
|
+
raise InvalidRequest, 'Header longer than allowed' if @data.size > MAX_HEADER
|
83
|
+
|
84
|
+
@nparsed = @parser.execute(@env, @data, @nparsed)
|
85
|
+
|
86
|
+
# Transfer to a tempfile if body is very big
|
87
|
+
move_body_to_tempfile if @parser.finished? && content_length > MAX_BODY
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
if finished? # Check if header and body are complete
|
92
|
+
@data = nil
|
93
|
+
@body.rewind
|
94
|
+
true # Request is fully parsed
|
95
|
+
else
|
96
|
+
false # Not finished, need more data
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# +true+ if headers and body are finished parsing
|
101
|
+
def finished?
|
102
|
+
@parser.finished? && @body.size >= content_length
|
103
|
+
end
|
104
|
+
|
105
|
+
# Expected size of the body
|
106
|
+
def content_length
|
107
|
+
@env[CONTENT_LENGTH].to_i
|
108
|
+
end
|
109
|
+
|
110
|
+
# Returns +true+ if the client expects the connection to be persistent.
|
111
|
+
def persistent?
|
112
|
+
# Clients and servers SHOULD NOT assume that a persistent connection
|
113
|
+
# is maintained for HTTP versions less than 1.1 unless it is explicitly
|
114
|
+
# signaled. (http://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html)
|
115
|
+
if @env[HTTP_VERSION] == HTTP_1_0
|
116
|
+
@env[CONNECTION] =~ KEEP_ALIVE_REGEXP
|
117
|
+
|
118
|
+
# HTTP/1.1 client intends to maintain a persistent connection unless
|
119
|
+
# a Connection header including the connection-token "close" was sent
|
120
|
+
# in the request
|
121
|
+
else
|
122
|
+
@env[CONNECTION].nil? || @env[CONNECTION] !~ CLOSE_REGEXP
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def remote_address=(address)
|
127
|
+
@env[REMOTE_ADDR] = address
|
128
|
+
end
|
129
|
+
|
130
|
+
def threaded=(value)
|
131
|
+
@env[RACK_MULTITHREAD] = value
|
132
|
+
end
|
133
|
+
|
134
|
+
def async_callback=(callback)
|
135
|
+
@env[ASYNC_CALLBACK] = callback
|
136
|
+
@env[ASYNC_CLOSE] = EventMachine::DefaultDeferrable.new
|
137
|
+
end
|
138
|
+
|
139
|
+
def async_close
|
140
|
+
@async_close ||= @env[ASYNC_CLOSE]
|
141
|
+
end
|
142
|
+
|
143
|
+
def head?
|
144
|
+
@env[REQUEST_METHOD] == HEAD
|
145
|
+
end
|
146
|
+
|
147
|
+
# Close any resource used by the request
|
148
|
+
def close
|
149
|
+
@body.close! if @body.class == Tempfile
|
150
|
+
end
|
151
|
+
|
152
|
+
private
|
153
|
+
def move_body_to_tempfile
|
154
|
+
current_body = @body
|
155
|
+
current_body.rewind
|
156
|
+
@body = Tempfile.new(BODY_TMPFILE)
|
157
|
+
@body.binmode
|
158
|
+
@body << current_body.read
|
159
|
+
@env[RACK_INPUT] = @body
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
module Thin
|
2
|
+
|
3
|
+
module VERSION #:nodoc:
|
4
|
+
MAJOR = 1
|
5
|
+
MINOR = 6
|
6
|
+
TINY = 3
|
7
|
+
|
8
|
+
STRING = [MAJOR, MINOR, TINY].join('.')
|
9
|
+
|
10
|
+
CODENAME = "Protein Powder".freeze
|
11
|
+
|
12
|
+
RACK = [1, 0].freeze # Rack protocol version
|
13
|
+
end
|
14
|
+
|
15
|
+
NAME = 'thin'.freeze
|
16
|
+
SERVER = "#{NAME} #{VERSION::STRING} codename #{VERSION::CODENAME}".freeze
|
17
|
+
|
18
|
+
HTTP_STATUS_CODES = {
|
19
|
+
100 => 'Continue',
|
20
|
+
101 => 'Switching Protocols',
|
21
|
+
200 => 'OK',
|
22
|
+
201 => 'Created',
|
23
|
+
202 => 'Accepted',
|
24
|
+
203 => 'Non-Authoritative Information',
|
25
|
+
204 => 'No Content',
|
26
|
+
205 => 'Reset Content',
|
27
|
+
206 => 'Partial Content',
|
28
|
+
300 => 'Multiple Choices',
|
29
|
+
301 => 'Moved Permanently',
|
30
|
+
302 => 'Moved Temporarily',
|
31
|
+
303 => 'See Other',
|
32
|
+
304 => 'Not Modified',
|
33
|
+
305 => 'Use Proxy',
|
34
|
+
400 => 'Bad Request',
|
35
|
+
401 => 'Unauthorized',
|
36
|
+
402 => 'Payment Required',
|
37
|
+
403 => 'Forbidden',
|
38
|
+
404 => 'Not Found',
|
39
|
+
405 => 'Method Not Allowed',
|
40
|
+
406 => 'Not Acceptable',
|
41
|
+
407 => 'Proxy Authentication Required',
|
42
|
+
408 => 'Request Time-out',
|
43
|
+
409 => 'Conflict',
|
44
|
+
410 => 'Gone',
|
45
|
+
411 => 'Length Required',
|
46
|
+
412 => 'Precondition Failed',
|
47
|
+
413 => 'Request Entity Too Large',
|
48
|
+
414 => 'Request-URI Too Large',
|
49
|
+
415 => 'Unsupported Media Type',
|
50
|
+
422 => 'Unprocessable Entity',
|
51
|
+
500 => 'Internal Server Error',
|
52
|
+
501 => 'Not Implemented',
|
53
|
+
502 => 'Bad Gateway',
|
54
|
+
503 => 'Service Unavailable',
|
55
|
+
504 => 'Gateway Time-out',
|
56
|
+
505 => 'HTTP Version not supported'
|
57
|
+
}
|
58
|
+
|
59
|
+
# A response sent to the client.
|
60
|
+
class Response
|
61
|
+
CONNECTION = 'Connection'.freeze
|
62
|
+
CLOSE = 'close'.freeze
|
63
|
+
KEEP_ALIVE = 'keep-alive'.freeze
|
64
|
+
SERVER = 'Server'.freeze
|
65
|
+
CONTENT_LENGTH = 'Content-Length'.freeze
|
66
|
+
|
67
|
+
PERSISTENT_STATUSES = [100, 101].freeze
|
68
|
+
|
69
|
+
#Error Responses
|
70
|
+
ERROR = [500, {'Content-Type' => 'text/plain'}, ['Internal server error']].freeze
|
71
|
+
PERSISTENT_ERROR = [500, {'Content-Type' => 'text/plain', 'Connection' => 'keep-alive', 'Content-Length' => "21"}, ['Internal server error']].freeze
|
72
|
+
BAD_REQUEST = [400, {'Content-Type' => 'text/plain'}, ['Bad Request']].freeze
|
73
|
+
|
74
|
+
# Status code
|
75
|
+
attr_accessor :status
|
76
|
+
|
77
|
+
# Response body, must respond to +each+.
|
78
|
+
attr_accessor :body
|
79
|
+
|
80
|
+
# Headers key-value hash
|
81
|
+
attr_reader :headers
|
82
|
+
|
83
|
+
def initialize
|
84
|
+
@headers = Headers.new
|
85
|
+
@status = 200
|
86
|
+
@persistent = false
|
87
|
+
@skip_body = false
|
88
|
+
end
|
89
|
+
|
90
|
+
# String representation of the headers
|
91
|
+
# to be sent in the response.
|
92
|
+
def headers_output
|
93
|
+
# Set default headers
|
94
|
+
@headers[CONNECTION] = persistent? ? KEEP_ALIVE : CLOSE unless @headers.has_key?(CONNECTION)
|
95
|
+
@headers[SERVER] = Thin::NAME unless @headers.has_key?(SERVER)
|
96
|
+
|
97
|
+
@headers.to_s
|
98
|
+
end
|
99
|
+
|
100
|
+
# Top header of the response,
|
101
|
+
# containing the status code and response headers.
|
102
|
+
def head
|
103
|
+
"HTTP/1.1 #{@status} #{HTTP_STATUS_CODES[@status.to_i]}\r\n#{headers_output}\r\n"
|
104
|
+
end
|
105
|
+
|
106
|
+
# if Thin.ruby_18?
|
107
|
+
|
108
|
+
# # Ruby 1.8 implementation.
|
109
|
+
# # Respects Rack specs.
|
110
|
+
# #
|
111
|
+
# # See http://rack.rubyforge.org/doc/files/SPEC.html
|
112
|
+
# def headers=(key_value_pairs)
|
113
|
+
# key_value_pairs.each do |k, vs|
|
114
|
+
# vs.each { |v| @headers[k] = v.chomp } if vs
|
115
|
+
# end if key_value_pairs
|
116
|
+
# end
|
117
|
+
|
118
|
+
# else
|
119
|
+
|
120
|
+
# Ruby 1.9 doesn't have a String#each anymore.
|
121
|
+
# Rack spec doesn't take care of that yet, for now we just use
|
122
|
+
# +each+ but fallback to +each_line+ on strings.
|
123
|
+
# I wish we could remove that condition.
|
124
|
+
# To be reviewed when a new Rack spec comes out.
|
125
|
+
def headers=(key_value_pairs)
|
126
|
+
key_value_pairs.each do |k, vs|
|
127
|
+
next unless vs
|
128
|
+
if vs.is_a?(Integer)
|
129
|
+
vs = vs.to_s
|
130
|
+
end
|
131
|
+
if vs.is_a?(String)
|
132
|
+
vs.each_line { |v| @headers[k] = v.chomp }
|
133
|
+
else
|
134
|
+
vs.each { |v| @headers[k] = v.chomp }
|
135
|
+
end
|
136
|
+
end if key_value_pairs
|
137
|
+
end
|
138
|
+
|
139
|
+
# end
|
140
|
+
|
141
|
+
# Close any resource used by the response
|
142
|
+
def close
|
143
|
+
@body.close if @body.respond_to?(:close)
|
144
|
+
end
|
145
|
+
|
146
|
+
# Yields each chunk of the response.
|
147
|
+
# To control the size of each chunk
|
148
|
+
# define your own +each+ method on +body+.
|
149
|
+
def each
|
150
|
+
yield head
|
151
|
+
|
152
|
+
unless @skip_body
|
153
|
+
if @body.is_a?(String)
|
154
|
+
yield @body
|
155
|
+
else
|
156
|
+
@body.each { |chunk| yield chunk }
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# Tell the client the connection should stay open
|
162
|
+
def persistent!
|
163
|
+
@persistent = true
|
164
|
+
end
|
165
|
+
|
166
|
+
# Persistent connection must be requested as keep-alive
|
167
|
+
# from the server and have a Content-Length, or the response
|
168
|
+
# status must require that the connection remain open.
|
169
|
+
def persistent?
|
170
|
+
(@persistent && @headers.has_key?(CONTENT_LENGTH)) || PERSISTENT_STATUSES.include?(@status)
|
171
|
+
end
|
172
|
+
|
173
|
+
def skip_body!
|
174
|
+
@skip_body = true
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|