syntropy 0.33.0 → 0.34.0

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.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +7 -0
  3. data/cmd/console.rb +18 -7
  4. data/cmd/serve.rb +26 -18
  5. data/cmd/test.rb +37 -24
  6. data/examples/blog/.gitignore +1 -0
  7. data/examples/blog/app/_lib/database.rb +13 -0
  8. data/examples/blog/app/_lib/{post_store.rb → posts.rb} +3 -1
  9. data/examples/blog/app/posts/[id]/edit.rb +2 -2
  10. data/examples/blog/app/posts/[id]/index.rb +4 -4
  11. data/examples/blog/app/posts/index.rb +4 -4
  12. data/examples/blog/app/posts/new.rb +1 -1
  13. data/examples/blog/config/development.rb +5 -0
  14. data/examples/blog/config/production.rb +4 -0
  15. data/examples/blog/config/test.rb +5 -0
  16. data/examples/blog/test/test_posts.rb +65 -0
  17. data/examples/mcp-oauth/app/oauth/token.rb +1 -1
  18. data/lib/syntropy/app.rb +48 -40
  19. data/lib/syntropy/applets/builtin/auto_refresh/watch.sse.rb +1 -1
  20. data/lib/syntropy/db/schema.rb +1 -1
  21. data/lib/syntropy/db/store.rb +2 -0
  22. data/lib/syntropy/errors.rb +6 -2
  23. data/lib/syntropy/http/client.rb +1 -0
  24. data/lib/syntropy/http/server_connection.rb +0 -4
  25. data/lib/syntropy/json_api.rb +27 -1
  26. data/lib/syntropy/logger.rb +81 -27
  27. data/lib/syntropy/markdown.rb +61 -32
  28. data/lib/syntropy/mime_types.rb +9 -5
  29. data/lib/syntropy/module_loader.rb +20 -9
  30. data/lib/syntropy/papercraft_extensions.rb +2 -2
  31. data/lib/syntropy/request/mock_adapter.rb +10 -8
  32. data/lib/syntropy/request/request_info.rb +91 -0
  33. data/lib/syntropy/request/response.rb +1 -12
  34. data/lib/syntropy/request/validation.rb +1 -0
  35. data/lib/syntropy/request.rb +51 -19
  36. data/lib/syntropy/routing_tree.rb +27 -28
  37. data/lib/syntropy/session.rb +198 -0
  38. data/lib/syntropy/side_run.rb +25 -2
  39. data/lib/syntropy/test.rb +105 -10
  40. data/lib/syntropy/utils.rb +53 -18
  41. data/lib/syntropy/version.rb +1 -1
  42. data/lib/syntropy.rb +44 -10
  43. data/test/bm_router_proc.rb +4 -4
  44. data/test/fixtures/app/class_instance.rb +5 -0
  45. data/test/fixtures/app/http.rb +5 -0
  46. data/test/fixtures/app/post_ct.rb +5 -0
  47. data/test/fixtures/app/singleton.rb +3 -0
  48. data/test/test_app.rb +13 -52
  49. data/test/test_caching.rb +2 -2
  50. data/test/test_db_schema.rb +1 -1
  51. data/test/test_http_server_connection.rb +3 -3
  52. data/test/test_module_loader.rb +5 -2
  53. data/test/test_response.rb +0 -19
  54. data/test/test_routing_tree.rb +69 -69
  55. data/test/test_server.rb +5 -9
  56. data/test/test_test.rb +70 -0
  57. metadata +52 -42
  58. data/examples/blog/app/_setup.rb +0 -4
  59. data/lib/syntropy/request/session.rb +0 -113
  60. /data/test/{app → fixtures/app}/.well-known/foo.rb +0 -0
  61. /data/test/{app → fixtures/app}/_hook.rb +0 -0
  62. /data/test/{app → fixtures/app}/_layout/default.rb +0 -0
  63. /data/test/{app → fixtures/app}/_lib/callable.rb +0 -0
  64. /data/test/{app → fixtures/app}/_lib/dep.rb +0 -0
  65. /data/test/{app → fixtures/app}/_lib/env.rb +0 -0
  66. /data/test/{app → fixtures/app}/_lib/klass.rb +0 -0
  67. /data/test/{app → fixtures/app}/_lib/missing-export.rb +0 -0
  68. /data/test/{app → fixtures/app}/_lib/self.rb +0 -0
  69. /data/test/{app → fixtures/app}/about/_error.rb +0 -0
  70. /data/test/{app → fixtures/app}/about/foo.md +0 -0
  71. /data/test/{app → fixtures/app}/about/index.rb +0 -0
  72. /data/test/{app → fixtures/app}/about/raise.rb +0 -0
  73. /data/test/{app → fixtures/app}/api+.rb +0 -0
  74. /data/test/{app → fixtures/app}/assets/style.css +0 -0
  75. /data/test/{app → fixtures/app}/bad_mod.rb +0 -0
  76. /data/test/{app → fixtures/app}/bar.rb +0 -0
  77. /data/test/{app → fixtures/app}/baz.rb +0 -0
  78. /data/test/{app → fixtures/app}/by_method.rb +0 -0
  79. /data/test/{app → fixtures/app}/deps.rb +0 -0
  80. /data/test/{app → fixtures/app}/index.html +0 -0
  81. /data/test/{app → fixtures/app}/mod/bar/index+.rb +0 -0
  82. /data/test/{app → fixtures/app}/mod/foo/index.rb +0 -0
  83. /data/test/{app → fixtures/app}/mod/path/a.rb +0 -0
  84. /data/test/{app → fixtures/app}/mod/path/b.rb +0 -0
  85. /data/test/{app → fixtures/app}/params/[foo].rb +0 -0
  86. /data/test/{app → fixtures/app}/rss.rb +0 -0
  87. /data/test/{app → fixtures/app}/tmp.rb +0 -0
  88. /data/test/{app_custom → fixtures/app_custom}/_site.rb +0 -0
  89. /data/test/{app_multi_site → fixtures/app_multi_site}/_site.rb +0 -0
  90. /data/test/{app_multi_site → fixtures/app_multi_site}/bar.baz/index.html +0 -0
  91. /data/test/{app_multi_site → fixtures/app_multi_site}/foo.bar/index.html +0 -0
  92. /data/test/{app_setup → fixtures/app_setup}/_setup.rb +0 -0
  93. /data/test/{app_setup → fixtures/app_setup}/index.rb +0 -0
  94. /data/test/{app_with_schema → fixtures/app_with_schema}/_schema/2026-01-02-foo.rb +0 -0
  95. /data/test/{app_with_schema → fixtures/app_with_schema}/_schema/2026-05-30-bar.rb +0 -0
  96. /data/test/{schema → fixtures/schema}/2026-01-02-foo.rb +0 -0
  97. /data/test/{schema → fixtures/schema}/2026-05-30-bar.rb +0 -0
@@ -28,7 +28,7 @@ module Syntropy
28
28
  # @return [void]
29
29
  def initialize(env)
30
30
  @env = env
31
- @root_dir = env[:root_dir]
31
+ @app_root = env[:app_root]
32
32
  @modules = {} # maps ref to module entry
33
33
  @fn_map = {} # maps filename to ref
34
34
  end
@@ -55,8 +55,8 @@ module Syntropy
55
55
  # @param dir [String] relative module directory
56
56
  # @return [Array<String>] list of modules
57
57
  def list(dir)
58
- fns = Dir[File.join(@root_dir, dir, '*.rb')]
59
- fns.map { it.match(/^#{@root_dir}\/(.+)\.rb$/)[1] }.sort
58
+ fns = Dir[File.join(@app_root, dir, '*.rb')]
59
+ fns.map { it.match(/^#{@app_root}\/(.+)\.rb$/)[1] }.sort
60
60
  end
61
61
 
62
62
  # Invalidates a module by its filename, normally following a change to the
@@ -89,10 +89,13 @@ module Syntropy
89
89
  entry[:reverse_deps].each { invalidate_ref(it) }
90
90
  end
91
91
 
92
+ # Invalidates a collection module.
93
+ #
94
+ # @return [void]
92
95
  def invalidate_collection_modules
93
96
  refs = []
94
97
  @modules.each do |ref, entry|
95
- refs << ref if entry[:module].is_collection_module?
98
+ refs << ref if entry[:module].collection_module?
96
99
  end
97
100
  refs.each { invalidate_ref(it) }
98
101
  end
@@ -118,7 +121,7 @@ module Syntropy
118
121
  # @return [Hash] module entry
119
122
  def load_module(ref, raise_on_missing: true)
120
123
  ref = "/#{ref}" if ref !~ /^\//
121
- fn = File.expand_path(File.join(@root_dir, "#{ref}.rb"))
124
+ fn = File.expand_path(File.join(@app_root, "#{ref}.rb"))
122
125
  if !File.file?(fn)
123
126
  raise Syntropy::Error, "File not found #{fn}" if raise_on_missing
124
127
 
@@ -142,6 +145,10 @@ module Syntropy
142
145
  }
143
146
  end
144
147
 
148
+ # Cleans up a module reference specifier, turning /index into /
149
+ #
150
+ # @param ref [String] input ref
151
+ # @return [String] clean ref
145
152
  def clean_ref(ref)
146
153
  return '/' if ref =~ /^index(\+)?$/
147
154
 
@@ -222,7 +229,7 @@ module Syntropy
222
229
  # #collection_module!
223
230
  #
224
231
  # @return [bool]
225
- def is_collection_module?
232
+ def collection_module?
226
233
  @collection_module_p
227
234
  end
228
235
 
@@ -258,6 +265,10 @@ module Syntropy
258
265
  self
259
266
  end
260
267
 
268
+ # Normalize an import reference, turning a relative path into an absolute one.
269
+ #
270
+ # @param ref [String] input ref
271
+ # @return [String] normalized ref
261
272
  def normalize_import_ref(ref)
262
273
  base = @ref == '' ? '/' : @ref
263
274
  if ref =~ /^\//
@@ -274,7 +285,7 @@ module Syntropy
274
285
  # @return [Papercraft::Template] template
275
286
  def template(proc = nil, &block)
276
287
  proc ||= block
277
- raise "No template block/proc given" if !proc
288
+ raise 'No template block/proc given' if !proc
278
289
 
279
290
  Papercraft::Template.new(proc)
280
291
  end
@@ -286,10 +297,10 @@ module Syntropy
286
297
  # @return [Papercraft::Template] template
287
298
  def template_xml(proc = nil, &block)
288
299
  proc ||= block
289
- raise "No template block/proc given" if !proc
300
+ raise 'No template block/proc given' if !proc
290
301
 
291
302
  Papercraft::Template.new(proc, mode: :xml)
292
- rescue => e
303
+ rescue StandardError => e
293
304
  p e
294
305
  p e.backtrace
295
306
  raise
@@ -4,12 +4,12 @@ require 'papercraft'
4
4
 
5
5
  Papercraft.extension(
6
6
  'auto_refresh!': ->(loc = '/.syntropy') {
7
- if $syntropy_dev_mode
7
+ if Syntropy.dev_mode
8
8
  script(src: File.join(loc, 'auto_refresh/watch.js'), type: 'module')
9
9
  end
10
10
  },
11
11
  'debug_template!': ->(loc = '/.syntropy') {
12
- if $syntropy_dev_mode
12
+ if Syntropy.dev_mode
13
13
  script(src: File.join(loc, 'debug/debug.js'), type: 'module')
14
14
  end
15
15
  }
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Syntropy
4
+ # Implements a mock adapter for testing
4
5
  class MockAdapter
5
6
  attr_reader :response_body, :response_headers, :calls
6
7
 
@@ -19,14 +20,13 @@ module Syntropy
19
20
  end
20
21
 
21
22
  def initialize(request_body)
22
- case request_body
23
- when Array
24
- @request_body_chunks = request_body
25
- when nil
26
- @request_body_chunks = []
27
- else
28
- @request_body_chunks = [request_body]
29
- end
23
+ @request_body_chunks =
24
+ case request_body
25
+ when Array then request_body
26
+ when nil then []
27
+ else [request_body]
28
+ end
29
+
30
30
  @calls = []
31
31
  end
32
32
 
@@ -47,6 +47,8 @@ module Syntropy
47
47
  response_headers[':status'] || HTTP::OK
48
48
  end
49
49
 
50
+ def respond_to_missing?(sym) = true
51
+
50
52
  def method_missing(sym, *args)
51
53
  calls << [sym, *args]
52
54
  end
@@ -3,36 +3,61 @@
3
3
  require 'uri'
4
4
 
5
5
  module Syntropy
6
+ # Request information extension methods.
6
7
  module RequestInfoMethods
8
+ # Returns the request host.
9
+ #
10
+ # @return [String, nil]
7
11
  def host
8
12
  @headers['host'] || @headers[':authority']
9
13
  end
10
14
  alias_method :authority, :host
11
15
 
16
+ # Returns the connection header value.
17
+ #
18
+ # @return [String, nil]
12
19
  def connection
13
20
  @headers['connection']
14
21
  end
15
22
 
23
+ # Returns the upgrade protocol.
24
+ #
25
+ # @return [String, nil]
16
26
  def upgrade_protocol
17
27
  connection == 'upgrade' && @headers['upgrade']&.downcase
18
28
  end
19
29
 
30
+ # Returns the websocket version.
31
+ #
32
+ # @return [String, nil]
20
33
  def websocket_version
21
34
  headers['sec-websocket-version'].to_i
22
35
  end
23
36
 
37
+ # Returns the protocol.
38
+ #
39
+ # @return [String, nil]
24
40
  def protocol
25
41
  @protocol ||= @adapter.protocol
26
42
  end
27
43
 
44
+ # Returns the HTTP method in lower case.
45
+ #
46
+ # @return [String]
28
47
  def method
29
48
  @method ||= @headers[':method'].downcase
30
49
  end
31
50
 
51
+ # Returns the request scheme.
52
+ #
53
+ # @return [String, nil]
32
54
  def scheme
33
55
  @scheme ||= @headers[':scheme']
34
56
  end
35
57
 
58
+ # Returns the request content type.
59
+ #
60
+ # @return [String, nil]
36
61
  def content_type
37
62
  ct = @headers['content-type']
38
63
  return nil if !ct
@@ -59,22 +84,37 @@ module Syntropy
59
84
  self
60
85
  end
61
86
 
87
+ # Returns the parsed request URI.
88
+ #
89
+ # @return [URI::Generic]
62
90
  def uri
63
91
  @uri ||= URI.parse(@headers[':path'] || '')
64
92
  end
65
93
 
94
+ # Returns the parsed full request URI.
95
+ #
96
+ # @return [URI::HTTP]
66
97
  def full_uri
67
98
  @full_uri = "#{scheme}://#{host}#{uri}"
68
99
  end
69
100
 
101
+ # Returns the request path.
102
+ #
103
+ # @return [String]
70
104
  def path
71
105
  @path ||= uri.path
72
106
  end
73
107
 
108
+ # Returns the request (unparsed) query string.
109
+ #
110
+ # @return [String, nil]
74
111
  def query_string
75
112
  @query_string ||= uri.query
76
113
  end
77
114
 
115
+ # Returns the parsed query hash.
116
+ #
117
+ # @return [Hash]
78
118
  def query
79
119
  return @query if @query
80
120
 
@@ -83,6 +123,10 @@ module Syntropy
83
123
 
84
124
  QUERY_KV_REGEXP = /([^=]+)(?:=(.*))?/
85
125
 
126
+ # Converts a query string into a query hash
127
+ #
128
+ # @param query [String]
129
+ # @return [Hash]
86
130
  def parse_query(query)
87
131
  query.split('&').each_with_object({}) do |kv, h|
88
132
  k, v = kv.match(QUERY_KV_REGEXP)[1..2]
@@ -90,10 +134,16 @@ module Syntropy
90
134
  end
91
135
  end
92
136
 
137
+ # Returns the request ID.
138
+ #
139
+ # @return [String, nil]
93
140
  def request_id
94
141
  @headers['x-request-id']
95
142
  end
96
143
 
144
+ # Returns the forwarded for value.
145
+ #
146
+ # @return [String, nil]
97
147
  def forwarded_for
98
148
  @headers['x-forwarded-for']
99
149
  end
@@ -107,6 +157,9 @@ module Syntropy
107
157
  encoding.split(',').map { |i| i.strip }
108
158
  end
109
159
 
160
+ # Returns the parsed cookie values.
161
+ #
162
+ # @return [String, nil]
110
163
  def cookies
111
164
  @cookies ||= parse_cookies(headers['cookie'])
112
165
  end
@@ -114,6 +167,10 @@ module Syntropy
114
167
  COOKIE_RE = /^([^=]+)=(.*)$/.freeze
115
168
  SEMICOLON = ';'
116
169
 
170
+ # Parses the cookie string.
171
+ #
172
+ # @param cookies [String]
173
+ # @return [Hash]
117
174
  def parse_cookies(cookies)
118
175
  return {} unless cookies
119
176
 
@@ -139,6 +196,9 @@ module Syntropy
139
196
  raise Syntropy::Error.new('Invalid form data', HTTP::BAD_REQUEST)
140
197
  end
141
198
 
199
+ # Returns true if the user-agent is a browser.
200
+ #
201
+ # @return [bool]
142
202
  def browser?
143
203
  user_agent = headers['user-agent']
144
204
  user_agent && user_agent =~ /^Mozilla\//
@@ -156,6 +216,9 @@ module Syntropy
156
216
  @accept_parts.include?(mime_type)
157
217
  end
158
218
 
219
+ # Returns the bearer token.
220
+ #
221
+ # @return [String, nil]
159
222
  def auth_bearer_token
160
223
  auth = headers['authorization']
161
224
  if auth && (m = auth.match(/Bearer\s+([^\w]+)/))
@@ -167,12 +230,22 @@ module Syntropy
167
230
 
168
231
  private
169
232
 
233
+ # Parses an accept string into an array of accepted MIME types.
234
+ #
235
+ # @param accept [string]
236
+ # @return [Array<String>]
170
237
  def parse_accept_parts(accept)
171
238
  accept.split(',').map { it.match(/^\s*([^\s;]+)/)[1] }
172
239
  end
173
240
  end
174
241
 
242
+ # Request info class methods
175
243
  module RequestInfoClassMethods
244
+ # Parses form data into a hash
245
+ #
246
+ # @param body [String]
247
+ # @param headers [Hash]
248
+ # @return [Hash]
176
249
  def parse_form_data(body, headers)
177
250
  case (content_type = headers['content-type'])
178
251
  when /^multipart\/form\-data; boundary=([^\s]+)/
@@ -185,6 +258,11 @@ module Syntropy
185
258
  end
186
259
  end
187
260
 
261
+ # Parses a multipart form body.
262
+ #
263
+ # @param body [String]
264
+ # @param boundary [String]
265
+ # @return [Hash]
188
266
  def parse_multipart_form_data(body, boundary)
189
267
  parts = body.split(boundary)
190
268
  raise BadRequestError, 'Invalid form data' if parts.size < 2
@@ -197,6 +275,11 @@ module Syntropy
197
275
  end
198
276
  end
199
277
 
278
+ # Parses a multipart form data part.
279
+ #
280
+ # @param body [String]
281
+ # @param hash [Hash] output hash
282
+ # @return [void]
200
283
  def parse_multipart_form_data_part(part, hash)
201
284
  body, headers = parse_multipart_form_data_part_headers(part)
202
285
  disposition = headers['content-disposition'] || ''
@@ -211,6 +294,10 @@ module Syntropy
211
294
  end
212
295
  end
213
296
 
297
+ # Parses a multipart form data part headers.
298
+ #
299
+ # @param part [String]
300
+ # @return [Hash]
214
301
  def parse_multipart_form_data_part_headers(part)
215
302
  headers = {}
216
303
  while true
@@ -234,6 +321,10 @@ module Syntropy
234
321
  MAX_PARAMETER_NAME_SIZE = 256
235
322
  MAX_PARAMETER_VALUE_SIZE = 2**20 # 1MB
236
323
 
324
+ # Parses a URL-encoded form.
325
+ #
326
+ # @param body [String]
327
+ # @return [Hash]
237
328
  def parse_urlencoded_form_data(body)
238
329
  return {} unless body
239
330
 
@@ -7,18 +7,7 @@ require_relative '../http/status'
7
7
  require_relative '../mime_types'
8
8
 
9
9
  module Syntropy
10
- module StaticFileCaching
11
- class << self
12
- def file_stat_to_etag(stat)
13
- "#{stat.mtime.to_i.to_s(36)}#{stat.size.to_s(36)}"
14
- end
15
-
16
- def file_stat_to_last_modified(stat)
17
- stat.mtime.httpdate
18
- end
19
- end
20
- end
21
-
10
+ # Response methods.
22
11
  module ResponseMethods
23
12
  WEBSOCKET_GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
24
13
 
@@ -3,6 +3,7 @@
3
3
  require 'uri'
4
4
 
5
5
  module Syntropy
6
+ # Request validation methods.
6
7
  module RequestValidationMethods
7
8
 
8
9
  # Checks the request's HTTP method against the given accepted values. If not
@@ -3,20 +3,26 @@
3
3
  require_relative './request/request_info'
4
4
  require_relative './request/validation'
5
5
  require_relative './request/response'
6
- require_relative './request/session'
6
+ require_relative './session'
7
7
  require_relative './http/status'
8
8
 
9
9
  module Syntropy
10
+ # Syntropy::Request represents an HTTP request. By interacting with the
11
+ # request, the app can extract request information and respond to the request.
10
12
  class Request
11
13
  include RequestInfoMethods
12
14
  include RequestValidationMethods
13
15
  include ResponseMethods
14
-
15
16
  extend RequestInfoClassMethods
16
17
 
17
18
  attr_reader :headers, :adapter, :start_stamp, :route_params
18
19
  attr_accessor :route
19
20
 
21
+ # Initializes the request.
22
+ #
23
+ # @param headers [Hash] request headers
24
+ # @param adapter [Object] connection adapter
25
+ # @return [void]
20
26
  def initialize(headers, adapter)
21
27
  @headers = headers
22
28
  @adapter = adapter
@@ -26,37 +32,62 @@ module Syntropy
26
32
  @ctx = nil
27
33
  end
28
34
 
29
- # Returns the request context
35
+ # Returns the request context, used to store auxiliary information.
36
+ #
37
+ # @return [Hash] request context hash
30
38
  def ctx
31
39
  @ctx ||= {}
32
40
  end
33
41
 
42
+ # Returns the next request body chunk.
43
+ #
44
+ # @return [String, nil]
34
45
  def next_chunk
35
46
  @adapter.get_body_chunk(self)
36
47
  end
37
48
 
49
+ # Reads request body chunks until the entire body is consumed, yielding each
50
+ # chunk to the given block.
51
+ #
52
+ # @return [void]
38
53
  def each_chunk
39
54
  while (chunk = @adapter.get_body_chunk(self))
40
55
  yield chunk
41
56
  end
42
57
  end
43
58
 
59
+ # Reads the request body.
60
+ #
61
+ # @return [String, nil] request body
44
62
  def read
45
63
  @adapter.get_body(self)
46
64
  end
47
65
  alias_method :body, :read
48
66
 
67
+ # Returns true if the request body has been consumed.
68
+ #
69
+ # @return [bool]
49
70
  def complete?
50
71
  @adapter.complete?(self)
51
72
  end
52
73
 
53
74
  EMPTY_HEADERS = {}.freeze
54
75
 
76
+ # Sends a response.
77
+ #
78
+ # @param body [String, nil] response body
79
+ # @param headers [Hash] response headers
80
+ # @return [void]
55
81
  def respond(body, headers = EMPTY_HEADERS)
56
82
  @adapter.respond(self, body, headers)
57
83
  @headers_sent = true
58
84
  end
59
85
 
86
+ # Sends response headers.
87
+ #
88
+ # @param headers [Hash] response headers
89
+ # @param empty_response [bool] body should be sent
90
+ # @return [void]
60
91
  def send_headers(headers = EMPTY_HEADERS, empty_response = false)
61
92
  return if @headers_sent
62
93
 
@@ -64,6 +95,11 @@ module Syntropy
64
95
  @adapter.send_headers(self, headers, empty_response: empty_response)
65
96
  end
66
97
 
98
+ # Sends a response body chunk.
99
+ #
100
+ # @param body [String] response body chunk
101
+ # @param done [bool] body is complete
102
+ # @return [void]
67
103
  def send_chunk(body, done: false)
68
104
  send_headers({}) unless @headers_sent
69
105
 
@@ -71,36 +107,32 @@ module Syntropy
71
107
  end
72
108
  alias_method :<<, :send_chunk
73
109
 
110
+ # Finish response.
111
+ #
112
+ # @return [void]
74
113
  def finish
75
114
  send_headers({}) unless @headers_sent
76
115
 
77
116
  @adapter.finish(self)
78
117
  end
79
118
 
119
+ # Returns true if response headers were sent.
120
+ #
121
+ # @return [bool]
80
122
  def headers_sent?
81
123
  @headers_sent
82
124
  end
83
125
 
84
- def rx_incr(count)
85
- headers[':rx'] ? headers[':rx'] += count : headers[':rx'] = count
86
- end
87
-
88
- def tx_incr(count)
89
- headers[':tx'] ? headers[':tx'] += count : headers[':tx'] = count
90
- end
91
-
92
- def transfer_counts
93
- [headers[':rx'], headers[':tx']]
94
- end
95
-
96
- def total_transfer
97
- (headers[':rx'] || 0) + (headers[':tx'] || 0)
98
- end
99
-
126
+ # Returns the request session.
127
+ #
128
+ # @return [Syntropy::Session]
100
129
  def session
101
130
  @session ||= Session.new(self)
102
131
  end
103
132
 
133
+ # Returns the request flash session storage.
134
+ #
135
+ # @return [Syntropy::Session::Flash]
104
136
  def flash
105
137
  session.flash
106
138
  end