knjappserver 0.0.16 → 0.0.17
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/VERSION +1 -1
- data/bin/knjappserver_start.rb +17 -25
- data/knjappserver.gemspec +17 -6
- data/lib/conf/conf_example.rb +3 -3
- data/lib/files/database_schema.rb +1 -2
- data/lib/include/class_customio.rb +8 -25
- data/lib/include/class_erbhandler.rb +7 -1
- data/lib/include/class_httpserver.rb +50 -34
- data/lib/include/class_httpsession.rb +138 -101
- data/lib/include/class_httpsession_contentgroup.rb +52 -24
- data/lib/include/class_httpsession_http_request.rb +203 -0
- data/lib/include/{class_httpresp.rb → class_httpsession_http_response.rb} +27 -28
- data/lib/include/class_httpsession_post_multipart.rb +117 -0
- data/lib/include/class_knjappserver.rb +146 -96
- data/lib/include/class_knjappserver_cleaner.rb +74 -68
- data/lib/include/class_knjappserver_cmdline.rb +23 -17
- data/lib/include/class_knjappserver_errors.rb +121 -104
- data/lib/include/class_knjappserver_leakproxy_client.rb +6 -0
- data/lib/include/class_knjappserver_leakproxy_server.rb +56 -0
- data/lib/include/class_knjappserver_logging.rb +25 -25
- data/lib/include/class_knjappserver_mailing.rb +84 -56
- data/lib/include/class_knjappserver_sessions.rb +15 -22
- data/lib/include/class_knjappserver_threadding.rb +70 -43
- data/lib/include/class_knjappserver_threadding_timeout.rb +20 -4
- data/lib/include/class_knjappserver_translations.rb +6 -4
- data/lib/include/class_knjappserver_web.rb +87 -35
- data/lib/include/class_log.rb +9 -9
- data/lib/include/class_log_link.rb +4 -4
- data/lib/include/class_session.rb +8 -4
- data/lib/include/gettext_funcs.rb +8 -6
- data/lib/include/magic_methods.rb +4 -0
- data/lib/pages/debug_database_connections.rhtml +46 -0
- data/lib/pages/debug_http_sessions.rhtml +40 -0
- data/lib/pages/error_notfound.rhtml +12 -0
- data/lib/pages/spec.rhtml +1 -1
- data/lib/pages/spec_post.rhtml +3 -0
- data/lib/pages/spec_thread_joins.rhtml +21 -0
- data/lib/pages/spec_threadded_content.rhtml +2 -0
- data/lib/pages/tests.rhtml +14 -0
- data/lib/scripts/benchmark.rb +25 -8
- data/lib/scripts/knjappserver_cgi.rb +60 -0
- data/lib/scripts/knjappserver_fcgi.rb +135 -0
- data/lib/scripts/leakproxy.rb +27 -0
- data/spec/knjappserver_spec.rb +16 -5
- data/spec/leakproxy_spec.rb +56 -0
- metadata +38 -27
- data/lib/include/class_httpsession_knjengine.rb +0 -154
- data/lib/include/class_httpsession_mongrel.rb +0 -75
- data/lib/include/class_httpsession_webrick.rb +0 -75
@@ -1,3 +1,5 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
|
1
3
|
#This class handels the adding of content and writing to socket. Since this can be done with multiple threads and multiple IO's it can get complicated.
|
2
4
|
class Knjappserver::Httpsession::Contentgroup
|
3
5
|
attr_reader :done, :cur_data
|
@@ -5,9 +7,10 @@ class Knjappserver::Httpsession::Contentgroup
|
|
5
7
|
NL = "\r\n"
|
6
8
|
|
7
9
|
def initialize(args = {})
|
8
|
-
@block = args[:restart_proc]
|
9
10
|
@socket = args[:socket]
|
10
11
|
@chunked = args[:chunked]
|
12
|
+
@resp = args[:resp]
|
13
|
+
@httpsession = args[:httpsession]
|
11
14
|
@mutex = Mutex.new
|
12
15
|
@debug = false
|
13
16
|
end
|
@@ -26,6 +29,7 @@ class Knjappserver::Httpsession::Contentgroup
|
|
26
29
|
@ios = []
|
27
30
|
@done = false
|
28
31
|
@thread = nil
|
32
|
+
@forced = false
|
29
33
|
|
30
34
|
@mutex.synchronize do
|
31
35
|
self.new_io
|
@@ -40,66 +44,89 @@ class Knjappserver::Httpsession::Contentgroup
|
|
40
44
|
@ios << @cur_data
|
41
45
|
end
|
42
46
|
|
47
|
+
#Forces the content to be the input - nothing else can be added after calling this.
|
48
|
+
def force_content(newcont)
|
49
|
+
@ios = [{:str => newcont, :done => true}]
|
50
|
+
end
|
51
|
+
|
43
52
|
def register_thread
|
44
53
|
Thread.current[:knjappserver] = {} if !Thread.current[:knjappserver]
|
45
54
|
Thread.current[:knjappserver][:contentgroup] = self
|
46
55
|
end
|
47
56
|
|
48
57
|
def new_thread
|
58
|
+
cgroup = Knjappserver::Httpsession::Contentgroup.new(:socket => @socket, :chunked => @chunked)
|
59
|
+
cgroup.init
|
60
|
+
|
49
61
|
@mutex.synchronize do
|
50
|
-
|
51
|
-
cgroup.init
|
52
|
-
|
53
|
-
data = {:cgroup => cgroup}
|
54
|
-
@ios << data
|
62
|
+
@ios << cgroup
|
55
63
|
self.new_io
|
56
|
-
|
57
|
-
|
58
|
-
|
64
|
+
end
|
65
|
+
|
66
|
+
self.register_thread
|
67
|
+
return cgroup
|
68
|
+
end
|
69
|
+
|
70
|
+
def write_begin
|
71
|
+
begin
|
72
|
+
@resp.write if @httpsession.meta["METHOD"] != "HEAD"
|
73
|
+
rescue Errno::ECONNRESET, Errno::ENOTCONN, Errno::EPIPE, Timeout::Error
|
74
|
+
#Ignore - the user probaly left.
|
59
75
|
end
|
60
76
|
end
|
61
77
|
|
62
78
|
def write(cont)
|
63
79
|
@mutex.synchronize do
|
64
|
-
@cur_data[:str] << cont
|
80
|
+
@cur_data[:str] << cont
|
65
81
|
end
|
66
82
|
end
|
67
83
|
|
68
84
|
def write_output
|
69
|
-
if @
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
85
|
+
return nil if @thread
|
86
|
+
|
87
|
+
@mutex.synchronize do
|
88
|
+
@thread = Thread.new do
|
89
|
+
self.write_begin
|
74
90
|
end
|
75
91
|
end
|
76
92
|
end
|
77
93
|
|
94
|
+
def write_force
|
95
|
+
@mutex.synchronize do
|
96
|
+
@forced = true if !@thread
|
97
|
+
end
|
98
|
+
|
99
|
+
if @thread
|
100
|
+
@thread.join
|
101
|
+
else
|
102
|
+
self.write_begin
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
78
106
|
def mark_done
|
79
107
|
@cur_data[:done] = true
|
80
108
|
@done = true
|
81
109
|
end
|
82
110
|
|
83
111
|
def join
|
84
|
-
if @
|
85
|
-
|
86
|
-
|
87
|
-
end
|
112
|
+
return nil if @forced
|
113
|
+
sleep 0.1 while !@thread
|
114
|
+
@thread.join
|
88
115
|
end
|
89
116
|
|
90
117
|
def write_to_socket
|
91
118
|
count = 0
|
92
119
|
|
93
120
|
@ios.each do |data|
|
94
|
-
if data.
|
95
|
-
data
|
121
|
+
if data.is_a?(Knjappserver::Httpsession::Contentgroup)
|
122
|
+
data.write_to_socket
|
96
123
|
elsif data.key?(:str)
|
97
124
|
if data[:str].is_a?(File)
|
98
125
|
file = data[:str]
|
99
126
|
|
100
127
|
loop do
|
101
128
|
begin
|
102
|
-
buf = file.sysread(
|
129
|
+
buf = file.sysread(16384)
|
103
130
|
rescue EOFError
|
104
131
|
break
|
105
132
|
end
|
@@ -115,7 +142,7 @@ class Knjappserver::Httpsession::Contentgroup
|
|
115
142
|
else
|
116
143
|
loop do
|
117
144
|
break if data[:done] and data[:str].size <= 0
|
118
|
-
sleep 0.1 while data[:str].size
|
145
|
+
sleep 0.1 while data[:str].size < 512 and !data[:done]
|
119
146
|
|
120
147
|
str = nil
|
121
148
|
@mutex.synchronize do
|
@@ -123,7 +150,8 @@ class Knjappserver::Httpsession::Contentgroup
|
|
123
150
|
data[:str] = ""
|
124
151
|
end
|
125
152
|
|
126
|
-
|
153
|
+
#512 could take a long time for big pages. 16384 seems to be an optimal number.
|
154
|
+
str.each_slice(16384) do |slice|
|
127
155
|
buf = slice.pack("C*")
|
128
156
|
|
129
157
|
if @chunked
|
@@ -0,0 +1,203 @@
|
|
1
|
+
require "uri"
|
2
|
+
|
3
|
+
#If we are running on JRuby or Rubinius this will seriously speed things up if we are behind a proxy.
|
4
|
+
if RUBY_PLATFORM == "java" or RUBY_ENGINE == "rbx"
|
5
|
+
BasicSocket.do_not_reverse_lookup = true
|
6
|
+
end
|
7
|
+
|
8
|
+
#This class parses the various HTTP requests into easy programmable objects. Get, post, cookie, meta and so on...
|
9
|
+
class Knjappserver::Httpsession::Http_request
|
10
|
+
attr_reader :get, :post, :cookie, :meta, :page_path, :headers, :http_version, :read, :clength, :speed, :percent, :secs_left
|
11
|
+
|
12
|
+
#Sets the various required data on the object. Knjappserver, crlf and arguments.
|
13
|
+
def initialize(args)
|
14
|
+
@args = args
|
15
|
+
@kas = @args[:kas]
|
16
|
+
@crlf = "\r\n"
|
17
|
+
end
|
18
|
+
|
19
|
+
#Reads content from the socket until the end of headers. Also does various error-checks.
|
20
|
+
def read_socket
|
21
|
+
loop do
|
22
|
+
raise Errno::ECONNRESET, "Socket closed." if @socket.closed?
|
23
|
+
read = @socket.gets
|
24
|
+
raise Errno::ECONNRESET, "Socket returned non-string: '#{read.class.name}'." if !read.is_a?(String)
|
25
|
+
@cont << read
|
26
|
+
break if @cont[-4..-1] == "\r\n\r\n" or @cont[-2..-1] == "\n\n"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
#Generates data on object from the given socket.
|
31
|
+
def socket_parse(socket)
|
32
|
+
@modified_since = nil
|
33
|
+
@cont = ""
|
34
|
+
@socket = socket
|
35
|
+
self.read_socket
|
36
|
+
|
37
|
+
#Parse URI (page_path and get).
|
38
|
+
match = @cont.match(/^(GET|POST|HEAD)\s+(.+)\s+HTTP\/1\.(\d+)\s*/)
|
39
|
+
raise "Could not parse request: '#{@cont.split("\n").first}'." if !match
|
40
|
+
|
41
|
+
@http_version = "1.#{match[3]}"
|
42
|
+
|
43
|
+
method = match[1]
|
44
|
+
@cont = @cont.gsub(match[0], "")
|
45
|
+
uri = URI.parse(match[2])
|
46
|
+
|
47
|
+
page_filepath = Knj::Web.urldec(uri.path)
|
48
|
+
if page_filepath.length <= 0 or page_filepath == "/" or File.directory?("#{@kas.config[:doc_root]}/#{page_filepath}")
|
49
|
+
page_filepath = "#{page_filepath}/#{@kas.config[:default_page]}"
|
50
|
+
end
|
51
|
+
|
52
|
+
@page_path = "#{@kas.config[:doc_root]}/#{page_filepath}"
|
53
|
+
@get = Knj::Web.parse_urlquery(uri.query.to_s, {:urldecode => true, :force_utf8 => true})
|
54
|
+
|
55
|
+
if @get["_kas_httpsession_id"]
|
56
|
+
@kas.httpsessions_ids[@get["_kas_httpsession_id"]] = @args[:httpsession]
|
57
|
+
end
|
58
|
+
|
59
|
+
begin
|
60
|
+
#Parse headers, cookies and meta.
|
61
|
+
@headers = {}
|
62
|
+
@cookie = {}
|
63
|
+
@meta = {
|
64
|
+
"REQUEST_METHOD" => method,
|
65
|
+
"QUERY_STRING" => uri.query,
|
66
|
+
"REQUEST_URI" => match[2],
|
67
|
+
"SCRIPT_NAME" => uri.path
|
68
|
+
}
|
69
|
+
|
70
|
+
@cont.scan(/^(\S+):\s*(.+)\r\n/) do |header_match|
|
71
|
+
key = header_match[0].downcase
|
72
|
+
val = header_match[1]
|
73
|
+
|
74
|
+
@headers[key] = [] if !@headers.has_key?(key)
|
75
|
+
@headers[key] << val
|
76
|
+
|
77
|
+
case key
|
78
|
+
when "cookie"
|
79
|
+
Knj::Web.parse_cookies(val).each do |key, val|
|
80
|
+
@cookie[key] = val
|
81
|
+
end
|
82
|
+
when "content-length"
|
83
|
+
@clength = val.to_i
|
84
|
+
else
|
85
|
+
key = key.upcase.gsub("-", "_")
|
86
|
+
@meta["HTTP_#{key}"] = val
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
#Parse post
|
92
|
+
@post = {}
|
93
|
+
|
94
|
+
if method == "POST"
|
95
|
+
post_treated = {}
|
96
|
+
|
97
|
+
@speed = nil
|
98
|
+
@read = 0
|
99
|
+
post_data = ""
|
100
|
+
|
101
|
+
Thread.new do
|
102
|
+
begin
|
103
|
+
time_cur = Time.now
|
104
|
+
read_last = 0
|
105
|
+
sleep 0.1
|
106
|
+
|
107
|
+
while @clength and @read != nil and @read < @clength
|
108
|
+
break if !@clength or !@read
|
109
|
+
|
110
|
+
time_now = Time.now
|
111
|
+
time_betw = time_now.to_f - time_cur.to_f
|
112
|
+
read_betw = @read - read_last
|
113
|
+
|
114
|
+
time_cur = time_now
|
115
|
+
read_last = @read
|
116
|
+
|
117
|
+
@percent = @read.to_f / @clength.to_f
|
118
|
+
@speed = read_betw.to_f / time_betw.to_f
|
119
|
+
|
120
|
+
bytes_left = @clength - read
|
121
|
+
|
122
|
+
if @speed > 0 and bytes_left > 0
|
123
|
+
@secs_left = bytes_left.to_f / @speed
|
124
|
+
else
|
125
|
+
@secs_left = false
|
126
|
+
end
|
127
|
+
|
128
|
+
sleep 2
|
129
|
+
end
|
130
|
+
rescue => e
|
131
|
+
if @kas
|
132
|
+
@kas.handle_error(e)
|
133
|
+
else
|
134
|
+
STDOUT.print Knj::Errors.error_str(e)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
while @read < @clength
|
140
|
+
read_size = @clength - @read
|
141
|
+
read_size = 4096 if read_size > 4096
|
142
|
+
|
143
|
+
raise Errno::ECONNRESET, "Socket closed." if @socket.closed?
|
144
|
+
read = @socket.read(read_size)
|
145
|
+
raise Errno::ECONNRESET, "Socket returned non-string: '#{read.class.name}'." if !read.is_a?(String)
|
146
|
+
post_data << read
|
147
|
+
@read += read.length
|
148
|
+
end
|
149
|
+
|
150
|
+
if @headers["content-type"] and match = @headers["content-type"].first.match(/^multipart\/form-data; boundary=(.+)\Z/)
|
151
|
+
post_treated = Knjappserver::Httpsession::Post_multipart.new(
|
152
|
+
"io" => StringIO.new("#{post_data}"),
|
153
|
+
"boundary" => match[1],
|
154
|
+
"crlf" => @crlf
|
155
|
+
).return
|
156
|
+
|
157
|
+
self.convert_post(@post, post_treated, {:urldecode => false})
|
158
|
+
else
|
159
|
+
post_data.split("&").each do |splitted|
|
160
|
+
splitted = splitted.split("=")
|
161
|
+
key = Knj::Web.urldec(splitted[0]).to_s.encode("utf-8")
|
162
|
+
val = splitted[1].to_s.encode("utf-8")
|
163
|
+
post_treated[key] = val
|
164
|
+
end
|
165
|
+
|
166
|
+
self.convert_post(@post, post_treated, {:urldecode => true})
|
167
|
+
end
|
168
|
+
end
|
169
|
+
ensure
|
170
|
+
@read = nil
|
171
|
+
@speed = nil
|
172
|
+
@clength = nil
|
173
|
+
@percent = nil
|
174
|
+
@secs_left = nil
|
175
|
+
|
176
|
+
#If it doesnt get unset we could have a serious memory reference GC problem.
|
177
|
+
if @get["_kas_httpsession_id"]
|
178
|
+
@kas.httpsessions_ids.delete(@get["_kas_httpsession_id"])
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
#Parses the if-modified-since header and returns it as a Time-object. Returns false is no if-modified-since-header is given or raises an RuntimeError if it cant be parsed.
|
184
|
+
def modified_since
|
185
|
+
return @modified_since if @modified_since
|
186
|
+
return false if !@meta["HTTP_IF_MODIFIED_SINCE"]
|
187
|
+
|
188
|
+
mod_match = @meta["HTTP_IF_MODIFIED_SINCE"].match(/^([A-z]+),\s+(\d+)\s+([A-z]+)\s+(\d+)\s+(\d+):(\d+):(\d+)\s+(.+)$/)
|
189
|
+
raise "Could not parse 'HTTP_IF_MODIFIED_SINCE'." if !mod_match
|
190
|
+
|
191
|
+
month_no = Knj::Datet.month_str_to_no(mod_match[3])
|
192
|
+
@modified_since = Time.utc(mod_match[4].to_i, month_no, mod_match[2].to_i, mod_match[5].to_i, mod_match[6].to_i, mod_match[7].to_i)
|
193
|
+
|
194
|
+
return @modified_since
|
195
|
+
end
|
196
|
+
|
197
|
+
#Converts post-result to the right type of hash.
|
198
|
+
def convert_post(seton, post_val, args = {})
|
199
|
+
post_val.each do |varname, value|
|
200
|
+
Knj::Web.parse_name(seton, varname, value, args)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
@@ -1,7 +1,8 @@
|
|
1
1
|
require "time"
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
#This object writes headers, trailing headers, status headers and more for HTTP-sessions.
|
4
|
+
class Knjappserver::Httpsession::Http_response
|
5
|
+
attr_accessor :chunked, :cgroup, :nl, :status, :http_version, :headers, :headers_trailing, :headers_sent, :socket
|
5
6
|
|
6
7
|
STATUS_CODES = {
|
7
8
|
100 => "Continue",
|
@@ -25,7 +26,8 @@ class Knjappserver::Httpresp
|
|
25
26
|
NL = "\r\n"
|
26
27
|
|
27
28
|
def initialize(args)
|
28
|
-
@
|
29
|
+
@chunked = false
|
30
|
+
@socket = args[:socket]
|
29
31
|
end
|
30
32
|
|
31
33
|
def reset(args)
|
@@ -73,57 +75,54 @@ class Knjappserver::Httpresp
|
|
73
75
|
end
|
74
76
|
|
75
77
|
code = STATUS_CODES[@status]
|
76
|
-
res
|
77
|
-
res
|
78
|
+
res << " #{code}" if code
|
79
|
+
res << NL
|
78
80
|
|
79
81
|
@headers.each do |key, val|
|
80
|
-
res
|
82
|
+
res << "#{val[0]}: #{val[1]}#{NL}"
|
81
83
|
end
|
82
84
|
|
83
85
|
if @http_version == "1.1"
|
84
86
|
@headers_11.each do |key, val|
|
85
|
-
res
|
87
|
+
res << "#{key}: #{val}#{NL}"
|
86
88
|
end
|
87
89
|
|
88
90
|
@trailers.each do |trailer|
|
89
|
-
res
|
91
|
+
res << "Trailer: #{trailer}#{NL}"
|
90
92
|
end
|
91
93
|
end
|
92
94
|
|
93
95
|
@cookies.each do |cookie|
|
94
|
-
res
|
96
|
+
res << "Set-Cookie: #{Knj::Web.cookie_str(cookie)}#{NL}"
|
95
97
|
end
|
96
98
|
|
97
|
-
res
|
99
|
+
res << NL
|
98
100
|
|
99
101
|
return res
|
100
102
|
end
|
101
103
|
|
102
|
-
def write
|
104
|
+
def write
|
103
105
|
@headers_sent = true
|
104
|
-
socket.write(self.header_str)
|
106
|
+
@socket.write(self.header_str)
|
105
107
|
|
106
108
|
if @status == 304
|
107
109
|
#do nothing.
|
108
110
|
else
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
@
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
socket.write(NL)
|
122
|
-
else
|
123
|
-
raise "Could not figure out of HTTP version: '#{@http_version}'."
|
111
|
+
if @chunked
|
112
|
+
@cgroup.write_to_socket
|
113
|
+
@socket.write("0#{NL}")
|
114
|
+
|
115
|
+
@headers_trailing.each do |header_id_str, header|
|
116
|
+
@socket.write("#{header[0]}: #{header[1]}#{NL}")
|
117
|
+
end
|
118
|
+
|
119
|
+
@socket.write(NL)
|
120
|
+
else
|
121
|
+
@cgroup.write_to_socket
|
122
|
+
@socket.write("#{NL}#{NL}")
|
124
123
|
end
|
125
124
|
end
|
126
125
|
|
127
|
-
socket.close if @close
|
126
|
+
@socket.close if @close
|
128
127
|
end
|
129
128
|
end
|