expressr 0.0.2 → 0.0.3
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.
- data/README.md +725 -0
- data/lib/expressr.rb +7 -1
- data/lib/expressr/app.rb +108 -0
- data/lib/expressr/json.rb +15 -0
- data/lib/expressr/listeners/request.rb +11 -0
- data/lib/expressr/listeners/response.rb +11 -0
- data/lib/expressr/renderer.rb +39 -0
- data/lib/expressr/renderers.rb +9 -0
- data/lib/expressr/renderers/base.rb +10 -0
- data/lib/expressr/renderers/haml.rb +13 -0
- data/lib/expressr/renderers/slim.rb +12 -0
- data/lib/expressr/request.rb +118 -0
- data/lib/expressr/response.rb +188 -0
- data/lib/expressr/route.rb +33 -0
- data/lib/expressr/route_item.rb +103 -0
- data/lib/expressr/route_node.rb +22 -0
- data/lib/expressr/router.rb +68 -0
- data/lib/expressr/utils.rb +46 -0
- data/lib/expressr/version.rb +1 -1
- metadata +98 -3
- data/Rakefile +0 -12
data/lib/expressr.rb
CHANGED
@@ -1,4 +1,10 @@
|
|
1
|
-
require
|
1
|
+
require 'mime-types'
|
2
|
+
require 'hashie'
|
3
|
+
require 'noder'
|
4
|
+
|
5
|
+
directory = File.dirname(File.absolute_path(__FILE__))
|
6
|
+
Dir.glob("#{directory}/expressr/*.rb") { |file| require file }
|
7
|
+
Dir.glob("#{directory}/expressr/listeners/*.rb") { |file| require file }
|
2
8
|
|
3
9
|
module Expressr
|
4
10
|
end
|
data/lib/expressr/app.rb
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
module Expressr
|
4
|
+
class App
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
attr_reader :router, :server
|
8
|
+
|
9
|
+
def_delegators :@router, :put, :post, :delete, :use, :route
|
10
|
+
|
11
|
+
DEFAULTS = {
|
12
|
+
'jsonp callback name' => 'callback',
|
13
|
+
'locals' => {},
|
14
|
+
'root' => nil,
|
15
|
+
'view engine' => 'slim',
|
16
|
+
'views' => 'views'
|
17
|
+
}
|
18
|
+
|
19
|
+
class << self
|
20
|
+
def settings
|
21
|
+
@settings ||= DEFAULTS
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(server_options={})
|
26
|
+
@router = Router.new
|
27
|
+
@locals = {}
|
28
|
+
set_default_root
|
29
|
+
@server_options = { app: self }.merge(server_options)
|
30
|
+
@server = Noder::HTTP::Server.new(@server_options)
|
31
|
+
@router.server = @server
|
32
|
+
request_stack = @server.event_stack('request')
|
33
|
+
request_stack.replace(Noder::HTTP::Listeners::Request, callback: Expressr::Listeners::Request)
|
34
|
+
request_stack.replace(Noder::HTTP::Listeners::Response, callback: Expressr::Listeners::Response)
|
35
|
+
end
|
36
|
+
|
37
|
+
def set(name, value)
|
38
|
+
settings[name] = value
|
39
|
+
end
|
40
|
+
|
41
|
+
def get(name_or_path, &block)
|
42
|
+
if block
|
43
|
+
@router.get(name_or_path, &block)
|
44
|
+
else
|
45
|
+
settings[name_or_path]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def enable(name)
|
50
|
+
settings[name] = true
|
51
|
+
end
|
52
|
+
|
53
|
+
def disable(name)
|
54
|
+
settings[name] = false
|
55
|
+
end
|
56
|
+
|
57
|
+
def enabled?(name)
|
58
|
+
settings[name] == true
|
59
|
+
end
|
60
|
+
|
61
|
+
def disabled?(name)
|
62
|
+
settings[name] == false
|
63
|
+
end
|
64
|
+
|
65
|
+
def engine(value)
|
66
|
+
set('view engine', value)
|
67
|
+
end
|
68
|
+
|
69
|
+
def param(name, &block)
|
70
|
+
@router.add_route(block, param: name)
|
71
|
+
end
|
72
|
+
|
73
|
+
def all(path, &block)
|
74
|
+
@router.add_route(block, path: path)
|
75
|
+
end
|
76
|
+
|
77
|
+
def locals
|
78
|
+
settings['locals']
|
79
|
+
end
|
80
|
+
|
81
|
+
def render(view, options={}, &block)
|
82
|
+
raise NotImplementedError
|
83
|
+
end
|
84
|
+
|
85
|
+
def listen(port=nil, address=nil, &block)
|
86
|
+
@server_options[:port] = port if port
|
87
|
+
@server_options[:address] = address if address
|
88
|
+
@server.listen(@server_options[:port], @server_options[:address], {}, &block)
|
89
|
+
end
|
90
|
+
|
91
|
+
def close
|
92
|
+
@server.close
|
93
|
+
end
|
94
|
+
|
95
|
+
def settings
|
96
|
+
self.class.settings
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def set_default_root
|
102
|
+
return if get('root')
|
103
|
+
caller_path = caller[1].split(':')[0]
|
104
|
+
caller_directory = File.dirname(caller_path)
|
105
|
+
set('root', Pathname.new(caller_directory))
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Expressr
|
2
|
+
class Renderer
|
3
|
+
class << self
|
4
|
+
attr_accessor :engine
|
5
|
+
|
6
|
+
def renderer
|
7
|
+
if engine != Expressr::App.settings['view engine']
|
8
|
+
@renderer = get_renderer
|
9
|
+
else
|
10
|
+
@renderer ||= get_renderer
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def get_renderer
|
15
|
+
self.engine = engines[Expressr::App.settings['view engine']]
|
16
|
+
raise "Invalid view engine value: #{engine}" unless engine
|
17
|
+
klass = Expressr::Utils.constantize(engine)
|
18
|
+
klass.new
|
19
|
+
end
|
20
|
+
|
21
|
+
def engines
|
22
|
+
{
|
23
|
+
'haml' => 'Expressr::Renderers::Haml',
|
24
|
+
'slim' => 'Expressr::Renderers::Slim'
|
25
|
+
}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def render(path, locals={})
|
30
|
+
path = App.settings['root'].join(App.settings['views'], path)
|
31
|
+
locals = App.settings['locals'].merge(locals)
|
32
|
+
renderer.render(path, locals)
|
33
|
+
end
|
34
|
+
|
35
|
+
def renderer
|
36
|
+
self.class.renderer
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
module Expressr
|
2
|
+
class Request < Noder::HTTP::Request
|
3
|
+
attr_reader :query
|
4
|
+
|
5
|
+
def initialize(env)
|
6
|
+
super(env)
|
7
|
+
@params = Hashie::Mash.new(@params)
|
8
|
+
@query = Hashie::Mash.new(@query)
|
9
|
+
end
|
10
|
+
|
11
|
+
def url
|
12
|
+
@url ||= original_url
|
13
|
+
end
|
14
|
+
|
15
|
+
def param(name)
|
16
|
+
params[name]
|
17
|
+
end
|
18
|
+
|
19
|
+
def route
|
20
|
+
raise NotImplementedError
|
21
|
+
end
|
22
|
+
|
23
|
+
def cookies
|
24
|
+
raise NotImplementedError
|
25
|
+
end
|
26
|
+
|
27
|
+
def signed_cookies
|
28
|
+
raise NotImplementedError
|
29
|
+
end
|
30
|
+
|
31
|
+
def get(name)
|
32
|
+
headers[name]
|
33
|
+
end
|
34
|
+
|
35
|
+
def accepts(types)
|
36
|
+
types = types.split(', ') if types.is_a?(String)
|
37
|
+
types = [types] unless types.is_a?(Array)
|
38
|
+
@accept_types ||= get('Accept').split(';').first.split(',')
|
39
|
+
(types & @accept_types).first
|
40
|
+
end
|
41
|
+
|
42
|
+
def accepts_charset(charset)
|
43
|
+
raise NotImplementedError
|
44
|
+
end
|
45
|
+
|
46
|
+
def accepts_language(language)
|
47
|
+
raise NotImplementedError
|
48
|
+
end
|
49
|
+
|
50
|
+
def is?(type)
|
51
|
+
content_type_header = headers['Content-Type']
|
52
|
+
return false if content_type_header.nil?
|
53
|
+
content_types = content_type_header.split('; ')
|
54
|
+
content_types.include?(type)
|
55
|
+
end
|
56
|
+
|
57
|
+
def ip
|
58
|
+
@env[:ip]
|
59
|
+
end
|
60
|
+
|
61
|
+
def ips
|
62
|
+
raise NotImplementedError
|
63
|
+
end
|
64
|
+
|
65
|
+
def path
|
66
|
+
@env[:request_uri]
|
67
|
+
end
|
68
|
+
|
69
|
+
def host
|
70
|
+
get('Host').split(':').first
|
71
|
+
end
|
72
|
+
|
73
|
+
def fresh?
|
74
|
+
raise NotImplementedError
|
75
|
+
end
|
76
|
+
|
77
|
+
def stale?
|
78
|
+
raise NotImplementedError
|
79
|
+
end
|
80
|
+
|
81
|
+
def xhr?
|
82
|
+
get('X-Requested-With') == 'XMLHttpRequest'
|
83
|
+
end
|
84
|
+
|
85
|
+
def protocol
|
86
|
+
@env[:protocol].split('/').first.downcase
|
87
|
+
end
|
88
|
+
|
89
|
+
def secure?
|
90
|
+
protocol == 'https'
|
91
|
+
end
|
92
|
+
|
93
|
+
def subdomains
|
94
|
+
host.split('.')[0..-3].reverse
|
95
|
+
end
|
96
|
+
|
97
|
+
def locals
|
98
|
+
@locals ||= Hashie::Mash.new
|
99
|
+
end
|
100
|
+
|
101
|
+
def original_url
|
102
|
+
@original_url ||= get_original_url
|
103
|
+
end
|
104
|
+
|
105
|
+
protected
|
106
|
+
|
107
|
+
def get_original_url
|
108
|
+
url = path
|
109
|
+
query_string = @env[:query_string]
|
110
|
+
if query_string && query_string != ''
|
111
|
+
url = "#{url}?#{query_string}"
|
112
|
+
end
|
113
|
+
url
|
114
|
+
end
|
115
|
+
|
116
|
+
alias_method :header, :get
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,188 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
|
3
|
+
module Expressr
|
4
|
+
class Response < Noder::HTTP::Response
|
5
|
+
DEFAULT_STATUS = 200
|
6
|
+
|
7
|
+
attr_accessor :cookies, :locals, :status
|
8
|
+
attr_reader :request
|
9
|
+
|
10
|
+
def initialize(env)
|
11
|
+
super(env)
|
12
|
+
@request = env[:request]
|
13
|
+
@cookies = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
# Defining #status(status) conflicts with EventMachine::DelegatedHttpResponse's #status
|
17
|
+
|
18
|
+
def set(key_or_hash, value=nil)
|
19
|
+
if key_or_hash.is_a?(Hash)
|
20
|
+
hash = key_or_hash
|
21
|
+
hash.each do |key, value|
|
22
|
+
set_header(key, value)
|
23
|
+
end
|
24
|
+
else
|
25
|
+
key = key_or_hash
|
26
|
+
set_header(key, value)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def get(key)
|
31
|
+
get_header(key)
|
32
|
+
end
|
33
|
+
|
34
|
+
def cookie(name, value, options={})
|
35
|
+
options = { 'name' => name, 'value' => value }.merge(options)
|
36
|
+
@cookies[name] = CGI::Cookie::new(options)
|
37
|
+
set_cookies
|
38
|
+
end
|
39
|
+
|
40
|
+
def clear_cookie(name, options={})
|
41
|
+
cookie(name, '', options)
|
42
|
+
end
|
43
|
+
|
44
|
+
def redirect(status_or_url, url=nil)
|
45
|
+
if url
|
46
|
+
status = status_or_url
|
47
|
+
else
|
48
|
+
status = 302
|
49
|
+
url = status_or_url
|
50
|
+
end
|
51
|
+
write_head(status)
|
52
|
+
location(url)
|
53
|
+
self.end
|
54
|
+
end
|
55
|
+
|
56
|
+
def location(url)
|
57
|
+
set_header('Location', url)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Equivalent of Express.js's send
|
61
|
+
def out(status_or_content=nil, content=nil)
|
62
|
+
if status_or_content.is_a?(Integer)
|
63
|
+
status = status_or_content
|
64
|
+
else
|
65
|
+
status = DEFAULT_STATUS
|
66
|
+
content = status_or_content || ''
|
67
|
+
end
|
68
|
+
if content.is_a?(Hash) || content.is_a?(Array)
|
69
|
+
json(status, content)
|
70
|
+
else
|
71
|
+
type('text/html') unless get('Content-Type')
|
72
|
+
write_head(status)
|
73
|
+
write(content)
|
74
|
+
self.end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def json(status_or_body, body=nil)
|
79
|
+
if status_or_body.is_a?(Integer)
|
80
|
+
status = status_or_body
|
81
|
+
else
|
82
|
+
status = DEFAULT_STATUS
|
83
|
+
body = status_or_body
|
84
|
+
end
|
85
|
+
type('application/json')
|
86
|
+
write_head(status)
|
87
|
+
write(Expressr::JSON.encode(body))
|
88
|
+
self.end
|
89
|
+
end
|
90
|
+
|
91
|
+
def jsonp(status_or_body, body=nil)
|
92
|
+
callback_name = Expressr::App.settings['jsonp callback name']
|
93
|
+
callback = params[callback_name]
|
94
|
+
if callback.nil?
|
95
|
+
json(status_or_body, body)
|
96
|
+
return
|
97
|
+
end
|
98
|
+
if status_or_body.is_a?(Integer)
|
99
|
+
status = status_or_body
|
100
|
+
else
|
101
|
+
status = DEFAULT_STATUS
|
102
|
+
body = status_or_body
|
103
|
+
end
|
104
|
+
type('application/javascript')
|
105
|
+
write_head(status)
|
106
|
+
body = Expressr::JSON.encode(body)
|
107
|
+
body = "#{callback}(#{body});"
|
108
|
+
write(body)
|
109
|
+
self.end
|
110
|
+
end
|
111
|
+
|
112
|
+
def type(value)
|
113
|
+
set_header('Content-Type', value)
|
114
|
+
end
|
115
|
+
|
116
|
+
def format(hash)
|
117
|
+
hash.each do |content_type, proc|
|
118
|
+
content_type = Utils.standardize_content_type(content_type)
|
119
|
+
if request.accepts(content_type)
|
120
|
+
proc.call(@request, self)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def attachment(filename=nil)
|
126
|
+
content_disposition = 'attachment'
|
127
|
+
if filename
|
128
|
+
basename = File.basename(filename)
|
129
|
+
content_disposition += %Q|; filename="#{basename}"|
|
130
|
+
content_type = MIME::Types.type_for(filename).first.to_s
|
131
|
+
type(content_type)
|
132
|
+
end
|
133
|
+
set_header('Content-Disposition', content_disposition)
|
134
|
+
end
|
135
|
+
|
136
|
+
def send_file(path, options={})
|
137
|
+
root = nil
|
138
|
+
if options[:root] && !path.start_with?('/')
|
139
|
+
root = Pathname.new(options[:root])
|
140
|
+
elsif App.settings['root']
|
141
|
+
root = App.settings['root']
|
142
|
+
end
|
143
|
+
path = root.join(path).to_s if root
|
144
|
+
attachment(path) unless options[:bypass_headers]
|
145
|
+
write(read_file(path))
|
146
|
+
self.end
|
147
|
+
end
|
148
|
+
|
149
|
+
def download(path, filename=nil)
|
150
|
+
attachment(filename || path)
|
151
|
+
send_file(path, bypass_headers: true)
|
152
|
+
end
|
153
|
+
|
154
|
+
def links(links)
|
155
|
+
value = links.map { |key, url| %Q|<#{url}>; rel="#{key}"| }.join(', ')
|
156
|
+
set_header('Link', value)
|
157
|
+
end
|
158
|
+
|
159
|
+
def locals
|
160
|
+
@locals ||= {}
|
161
|
+
end
|
162
|
+
|
163
|
+
def render(view, locals=nil, &block)
|
164
|
+
body = nil
|
165
|
+
begin
|
166
|
+
body = Renderer.new.render(view, locals)
|
167
|
+
rescue Exception => e
|
168
|
+
if block
|
169
|
+
block.call(e)
|
170
|
+
else
|
171
|
+
raise
|
172
|
+
end
|
173
|
+
end
|
174
|
+
out(body)
|
175
|
+
end
|
176
|
+
|
177
|
+
protected
|
178
|
+
|
179
|
+
def read_file(path)
|
180
|
+
File.open(path, 'rb').read
|
181
|
+
end
|
182
|
+
|
183
|
+
def set_cookies
|
184
|
+
cookie_string = @cookies.values.map(&:to_s).join("\nSet-Cookie: ")
|
185
|
+
set_header('Set-Cookie', cookie_string)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|