syntropy 0.30.0 → 0.31.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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +17 -0
  3. data/bin/syntropy +8 -86
  4. data/cmd/_banner.rb +16 -0
  5. data/cmd/help.rb +12 -0
  6. data/cmd/serve.rb +95 -0
  7. data/cmd/test.rb +40 -0
  8. data/examples/{counter.rb → basic/counter.rb} +1 -1
  9. data/examples/{templates.rb → basic/templates.rb} +1 -1
  10. data/examples/mcp-oauth/.ruby-version +1 -0
  11. data/examples/mcp-oauth/Gemfile +8 -0
  12. data/examples/mcp-oauth/README.md +128 -0
  13. data/examples/mcp-oauth/app/.well-known/oauth-authorization-server.rb +18 -0
  14. data/examples/mcp-oauth/app/.well-known/oauth-protected-resource.rb +10 -0
  15. data/examples/mcp-oauth/app/_lib/auth_store.rb +23 -0
  16. data/examples/mcp-oauth/app/index.md +1 -0
  17. data/examples/mcp-oauth/app/mcp.rb +38 -0
  18. data/examples/mcp-oauth/app/oauth/authorize.rb +26 -0
  19. data/examples/mcp-oauth/app/oauth/consent.rb +86 -0
  20. data/examples/mcp-oauth/app/oauth/register.rb +15 -0
  21. data/examples/mcp-oauth/app/oauth/token.rb +79 -0
  22. data/examples/mcp-oauth/app/signin.rb +85 -0
  23. data/examples/mcp-oauth/test/helper.rb +9 -0
  24. data/examples/mcp-oauth/test/test_app.rb +27 -0
  25. data/examples/mcp-oauth/test/test_oauth.rb +628 -0
  26. data/lib/syntropy/app.rb +15 -4
  27. data/lib/syntropy/applets/builtin/default_error_handler.rb +3 -3
  28. data/lib/syntropy/applets/builtin/req.rb +1 -1
  29. data/lib/syntropy/dev_mode.rb +1 -1
  30. data/lib/syntropy/errors.rb +6 -0
  31. data/lib/syntropy/http/client.rb +43 -0
  32. data/lib/syntropy/http/client_connection.rb +36 -0
  33. data/lib/syntropy/http/io_extensions.rb +148 -0
  34. data/lib/syntropy/http/server.rb +5 -5
  35. data/lib/syntropy/http/{connection.rb → server_connection.rb} +9 -38
  36. data/lib/syntropy/http.rb +3 -1
  37. data/lib/syntropy/logger.rb +5 -1
  38. data/lib/syntropy/papercraft_extensions.rb +1 -1
  39. data/lib/syntropy/request/mock_adapter.rb +2 -0
  40. data/lib/syntropy/request/request_info.rb +20 -1
  41. data/lib/syntropy/request/response.rb +2 -2
  42. data/lib/syntropy/request/validation.rb +10 -3
  43. data/lib/syntropy/routing_tree.rb +2 -1
  44. data/lib/syntropy/test.rb +65 -0
  45. data/lib/syntropy/version.rb +1 -1
  46. data/lib/syntropy.rb +1 -21
  47. data/syntropy.gemspec +1 -2
  48. data/test/app/.well-known/foo.rb +3 -0
  49. data/test/helper.rb +1 -25
  50. data/test/test_app.rb +53 -68
  51. data/test/test_caching.rb +1 -1
  52. data/test/test_http_client.rb +52 -0
  53. data/test/test_http_client_connection.rb +43 -0
  54. data/test/{test_connection.rb → test_http_server_connection.rb} +29 -29
  55. data/test/test_json_api.rb +4 -4
  56. data/test/{test_request_extensions.rb → test_request.rb} +150 -18
  57. data/test/test_routing_tree.rb +15 -3
  58. metadata +41 -29
  59. data/test/test_request_info.rb +0 -90
  60. /data/examples/{bad.rb → basic/bad.rb} +0 -0
  61. /data/examples/{card.rb → basic/card.rb} +0 -0
  62. /data/examples/{counter.js → basic/counter.js} +0 -0
  63. /data/examples/{counter_api.rb → basic/counter_api.rb} +0 -0
  64. /data/examples/{favicon.ico → basic/favicon.ico} +0 -0
  65. /data/examples/{index.md → basic/index.md} +0 -0
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  export ->(req) {
4
- req.json_response({
4
+ req.respond_json({
5
5
  headers: req.headers
6
6
  })
7
7
  }
@@ -4,7 +4,7 @@ TAG_DEBUG_PROC = ->(level, fn, line, col) {
4
4
  {
5
5
  'data-syntropy-level' => level,
6
6
  'data-syntropy-fn' => fn,
7
- 'data-syntropy-loc' => "vscode://file/#{fn}:#{line}:#{col}"
7
+ 'data-syntropy-loc' => "zed://file/#{fn}:#{line}:#{col}"
8
8
  }
9
9
  }
10
10
 
@@ -84,4 +84,10 @@ module Syntropy
84
84
 
85
85
  class BadRequestError < Error
86
86
  end
87
+
88
+ class InvalidRequestContentTypeError < Error
89
+ def http_status
90
+ HTTP::UNSUPPORTED_MEDIA_TYPE
91
+ end
92
+ end
87
93
  end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'syntropy/http/client_connection'
4
+ require 'uri'
5
+
6
+ module Syntropy
7
+ module HTTP
8
+ class Client
9
+ def initialize(machine)
10
+ @machine = machine
11
+ end
12
+
13
+ def get(url, **headers, &)
14
+ uri = URI.parse(url)
15
+ headers = headers.merge(
16
+ ':method' => 'GET',
17
+ ':path' => uri.request_uri
18
+ )
19
+ req(uri, **headers, &)
20
+ end
21
+
22
+ private
23
+
24
+ # @param uri [URI]
25
+ def req(uri, **headers)
26
+ connection = make_connection(uri.scheme, uri.host, uri.port)
27
+ response_headers = connection.req(**headers)
28
+ if block_given?
29
+ yield(response_headers, connection)
30
+ else
31
+ [response_headers, connection.get_response_body(response_headers)]
32
+ end
33
+ end
34
+
35
+ def make_connection(_scheme, host, port)
36
+ ip = (host =~ /^\d+\.\d+\.\d+\.\d+$/) ? host : @machine.resolve(host)[0]
37
+
38
+ fd = @machine.tcp_connect(ip, port)
39
+ Syntropy::HTTP::ClientConnection.new(@machine, fd)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'syntropy/errors'
4
+ require 'syntropy/http/io_extensions'
5
+
6
+ module Syntropy
7
+ module HTTP
8
+ class ClientConnection
9
+ attr_reader :fd, :response_headers, :logger
10
+
11
+ def initialize(machine, fd, io_mode: :socket)
12
+ @machine = machine
13
+ @fd = fd
14
+ @io = machine.io(fd, io_mode)
15
+ end
16
+
17
+ def req(body: nil, **headers)
18
+ if body
19
+ headers = headers.merge(
20
+ 'Content-Length' => body.bytesize
21
+ )
22
+ end
23
+ @io.http_write_request_headers(**headers)
24
+ if body
25
+ @io.write(body)
26
+ end
27
+
28
+ @io.http_read_response_headers
29
+ end
30
+
31
+ def get_response_body(headers)
32
+ @io.http_read_body(headers)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'syntropy/errors'
4
+
5
+ module Syntropy
6
+ module HTTP
7
+ module ProtocolMethods
8
+ RE_REQUEST_LINE = /^([a-z]+)\s+([^\s]+)\s+HTTP\/([019\.]{1,3})/i
9
+ RE_RESPONSE_LINE = /^HTTP\/1\.1\s+(\d{3})(\s+.+)?$/i
10
+ RE_HEADER_LINE = /^([a-z0-9-]+):\s+(.+)/i
11
+
12
+ MAX_REQUEST_LINE_LEN = 1 << 14 # 16KB
13
+ MAX_RESPONSE_LINE_LEN = 1 << 8 # 256
14
+ MAX_HEADER_LINE_LEN = 1 << 10 # 1KB
15
+ MAX_CHUNK_SIZE_LEN = 16
16
+
17
+ # @return [Hash] headers
18
+ def http_read_request_headers
19
+ line = read_line(MAX_REQUEST_LINE_LEN)
20
+ return nil if !line
21
+
22
+ m = line.match(RE_REQUEST_LINE)
23
+ raise ProtocolError, 'Invalid request line' if !m
24
+
25
+ http_version = m[3]
26
+ raise UnsupportedHTTPVersionError, 'HTTP version not supported' if http_version != '1.1'
27
+
28
+ headers = {
29
+ ':method' => m[1].downcase,
30
+ ':path' => m[2],
31
+ ':protocol' => 'http/1.1'
32
+ }
33
+
34
+ loop do
35
+ line = read_line(MAX_HEADER_LINE_LEN)
36
+ break if line.nil? || line.empty?
37
+
38
+ m = line.match(RE_HEADER_LINE)
39
+ raise ProtocolError, "Invalid header: #{line[0..2047].inspect}" if !m
40
+
41
+ headers[m[1].downcase] = m[2]
42
+ end
43
+
44
+ headers
45
+ end
46
+
47
+ def http_read_response_headers
48
+ line = read_line(MAX_RESPONSE_LINE_LEN)
49
+ return nil if !line
50
+
51
+ m = line.match(RE_RESPONSE_LINE)
52
+ raise ProtocolError, 'Invalid response line' if !m
53
+
54
+ headers = {
55
+ ':status' => m[1].to_i
56
+ }
57
+
58
+ loop do
59
+ line = read_line(MAX_HEADER_LINE_LEN)
60
+ break if line.nil? || line.empty?
61
+
62
+ m = line.match(RE_HEADER_LINE)
63
+ raise ProtocolError, "Invalid header: #{line[0..2047].inspect}" if !m
64
+
65
+ k = m[1].downcase
66
+ if (h = headers[k])
67
+ (h = headers[k] = [h]) if !h.is_a?(Array)
68
+ h << m[2]
69
+ else
70
+ headers[k] = m[2]
71
+ end
72
+ end
73
+
74
+ headers
75
+ end
76
+
77
+ def http_read_body(headers)
78
+ content_length = headers['content-length']
79
+ if content_length
80
+ chunk = read(content_length.to_i)
81
+ return chunk
82
+ end
83
+
84
+ chunked_encoding = headers['transfer-encoding']&.downcase == 'chunked'
85
+ if chunked_encoding
86
+ buf = +''
87
+ while (chunk = http_read_cte_chunk(nil))
88
+ buf << chunk
89
+ end
90
+ return buf
91
+ end
92
+
93
+ nil
94
+ end
95
+
96
+ def http_read_body_chunk(headers)
97
+ content_length = headers['content-length']
98
+ if content_length
99
+ chunk = read(content_length.to_i)
100
+ return chunk
101
+ end
102
+
103
+ chunked_encoding = headers['transfer-encoding']&.downcase == 'chunked'
104
+ return http_read_cte_chunk(nil) if chunked_encoding
105
+
106
+ nil
107
+ end
108
+
109
+ def http_write_request_headers(headers)
110
+ method = headers[':method'] || (raise BadRequestError)
111
+ path = headers[':path'] || (raise BadRequestError)
112
+
113
+ lines = ["#{method} #{path} HTTP/1.1\r\n"]
114
+ headers.each do |k, v|
115
+ next if k =~ /^\:/
116
+
117
+ if v.is_a?(Array)
118
+ v.each { lines << "#{k}: #{it}\r\n" }
119
+ else
120
+ lines << "#{k}: #{v}\r\n"
121
+ end
122
+ end
123
+ lines << "\r\n"
124
+ write(*lines)
125
+ end
126
+
127
+ private
128
+
129
+ def http_read_cte_chunk(buffer)
130
+ chunk_size_str = read_line(MAX_CHUNK_SIZE_LEN)
131
+ return nil if !chunk_size_str
132
+
133
+ chunk_size = chunk_size_str.to_i(16)
134
+ if chunk_size == 0
135
+ read_line(0)
136
+ return nil
137
+ end
138
+
139
+ chunk = read(chunk_size)
140
+ read_line(0)
141
+
142
+ buffer ? (buffer << chunk) : chunk
143
+ end
144
+ end
145
+ end
146
+ end
147
+
148
+ UringMachine::IO.include(Syntropy::HTTP::ProtocolMethods)
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'syntropy/http/connection'
3
+ require 'syntropy/http/server_connection'
4
4
 
5
5
  module Syntropy
6
6
  module HTTP
@@ -119,13 +119,13 @@ module Syntropy
119
119
  end
120
120
 
121
121
  def accept_incoming(listen_fd)
122
- @machine.accept_each(listen_fd) { start_client_connection(it) }
122
+ @machine.accept_each(listen_fd) { start_connection(it) }
123
123
  rescue UM::Terminate
124
- # terminated
124
+ @machine.shutdown(listen_fd, UM::SHUT_RD)
125
125
  end
126
126
 
127
- def start_client_connection(fd)
128
- conn = Connection.new(self, @machine, fd, @env, &@app)
127
+ def start_connection(fd)
128
+ conn = ServerConnection.new(@machine, fd, @env, &@app)
129
129
  f = @machine.spin(conn) do
130
130
  it.run
131
131
  ensure
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'syntropy/errors'
4
+ require 'syntropy/http/io_extensions'
4
5
 
5
6
  module Syntropy
6
7
  module HTTP
@@ -9,16 +10,15 @@ module Syntropy
9
10
  # body is sent exclusively using chunked transfer encoding. Request bodies are
10
11
  # accepted using either fixed length (Content-Length header) or chunked
11
12
  # transfer encoding.
12
- class Connection
13
+ class ServerConnection
13
14
  attr_reader :fd, :response_headers, :logger
14
15
 
15
- def initialize(server, machine, fd, env, &app)
16
- @server = server
16
+ def initialize(machine, fd, env, io_mode: :socket, &app)
17
17
  @machine = machine
18
18
  @fd = fd
19
19
  @env = env
20
20
  @logger = env[:logger]
21
- @io = machine.io(fd, :socket)
21
+ @io = machine.io(fd, io_mode)
22
22
  @app = app
23
23
 
24
24
  @done = nil
@@ -96,46 +96,17 @@ module Syntropy
96
96
  headers = req.headers
97
97
  return nil if headers[':body-done-reading']
98
98
 
99
- content_length = headers['content-length']
100
- if content_length
101
-
102
- chunk = @io.read(content_length.to_i)
103
- headers[':body-done-reading'] = true
104
- return chunk
105
- end
106
-
107
- chunked_encoding = headers['transfer-encoding']&.downcase == 'chunked'
108
- if chunked_encoding
109
- buf = +''
110
- while (chunk = read_chunk(headers, nil))
111
- buf << chunk
112
- end
113
- headers[':body-done-reading'] = true
114
- return buf
115
- end
116
-
117
- nil
99
+ body = @io.http_read_body(headers)
100
+ headers[':body-done-reading'] = true if body
101
+ body
118
102
  end
119
103
 
120
104
  def get_body_chunk(req)
121
105
  headers = req.headers
122
- content_length = headers['content-length']
123
- if content_length
124
- return nil if headers[':body-done-reading']
125
-
126
- chunk = @io.read(content_length.to_i)
127
- headers[':body-done-reading'] = true
128
- return chunk
129
- end
130
-
131
- chunked_encoding = headers['transfer-encoding']&.downcase == 'chunked'
132
- return read_chunk(headers, nil) if chunked_encoding
133
-
134
106
  return nil if headers[':body-done-reading']
135
107
 
136
- # if content-length is not specified, we read to EOF, up to max 1MB size
137
- chunk = read(1 << 20, nil, false)
138
- headers[':body-done-reading'] = true
108
+ chunk = @io.http_read_body_chunk(headers)
109
+ headers[':body-done-reading'] = true if !chunk
139
110
  chunk
140
111
  end
141
112
 
data/lib/syntropy/http.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'syntropy/http/status'
4
- require 'syntropy/http/connection'
4
+ require 'syntropy/http/client_connection'
5
+ require 'syntropy/http/client'
6
+ require 'syntropy/http/server_connection'
5
7
  require 'syntropy/http/server'
@@ -67,7 +67,7 @@ module Syntropy
67
67
  request = o[:request]
68
68
  request_headers = request.headers
69
69
  response_headers = o[:response_headers]
70
- elapsed = request.adapter.monotonic_clock - request.start_stamp
70
+ elapsed = monotonic_clock - request.start_stamp
71
71
  {
72
72
  level: level.to_s,
73
73
  ts: (t = Time.now; t.to_i),
@@ -82,6 +82,10 @@ module Syntropy
82
82
  }
83
83
  end
84
84
 
85
+ def monotonic_clock
86
+ ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
87
+ end
88
+
85
89
  def make_hash_entry(level, hash)
86
90
  {
87
91
  level: level.to_s,
@@ -3,7 +3,7 @@
3
3
  require 'papercraft'
4
4
 
5
5
  Papercraft.extension(
6
- 'auto_refresh_watch!': ->(loc = '/.syntropy') {
6
+ 'auto_refresh!': ->(loc = '/.syntropy') {
7
7
  if $syntropy_dev_mode
8
8
  script(src: File.join(loc, 'auto_refresh/watch.js'), type: 'module')
9
9
  end
@@ -42,6 +42,8 @@ module Syntropy
42
42
  end
43
43
 
44
44
  def status
45
+ raise 'No response' if !response_headers
46
+
45
47
  response_headers[':status'] || HTTP::OK
46
48
  end
47
49
 
@@ -34,6 +34,16 @@ module Syntropy
34
34
  @scheme ||= @headers[':scheme']
35
35
  end
36
36
 
37
+ def content_type
38
+ ct = @headers['content-type']
39
+ return nil if !ct
40
+
41
+ m = ct.match(/^([^;]+)/)
42
+ return nil if !m
43
+
44
+ m[1].strip
45
+ end
46
+
37
47
  # Rewrites the request path by replacing the given src with the given
38
48
  # replacement.
39
49
  #
@@ -77,7 +87,7 @@ module Syntropy
77
87
  def parse_query(query)
78
88
  query.split('&').each_with_object({}) do |kv, h|
79
89
  k, v = kv.match(QUERY_KV_REGEXP)[1..2]
80
- h[k.to_sym] = v ? URI.decode_www_form_component(v) : true
90
+ h[k] = v ? URI.decode_www_form_component(v) : true
81
91
  end
82
92
  end
83
93
 
@@ -147,6 +157,15 @@ module Syntropy
147
157
  @accept_parts.include?(mime_type)
148
158
  end
149
159
 
160
+ def auth_bearer_token
161
+ auth = headers['authorization']
162
+ if (m = auth.match(/Bearer\s+([^\w]+)/))
163
+ return m[1]
164
+ end
165
+
166
+ nil
167
+ end
168
+
150
169
  private
151
170
 
152
171
  def parse_accept_parts(accept)
@@ -179,7 +179,7 @@ module Syntropy
179
179
  end
180
180
  end
181
181
 
182
- def html_response(html, **headers)
182
+ def respond_html(html, **headers)
183
183
  respond(
184
184
  html,
185
185
  'Content-Type' => 'text/html; charset=utf-8',
@@ -187,7 +187,7 @@ module Syntropy
187
187
  )
188
188
  end
189
189
 
190
- def json_response(obj, **headers)
190
+ def respond_json(obj, **headers)
191
191
  respond(
192
192
  JSON.dump(obj),
193
193
  'Content-Type' => 'application/json; charset=utf-8',
@@ -18,6 +18,13 @@ module Syntropy
18
18
  raise Syntropy::Error.method_not_allowed
19
19
  end
20
20
 
21
+ def validate_content_type(*accepted)
22
+ ct = content_type
23
+ return ct if accepted.include?(ct)
24
+
25
+ raise Syntropy::InvalidRequestContentTypeError
26
+ end
27
+
21
28
  # Validates and optionally converts request parameter value for the given
22
29
  # parameter name against the given clauses. If no clauses are given,
23
30
  # verifies the parameter value is not nil. A clause can be a class, such as
@@ -47,12 +54,12 @@ module Syntropy
47
54
  # @param value [any] value
48
55
  # @clauses [Array] one or more validation clauses
49
56
  # @return [any] validated value
50
- def validate(value, *clauses)
51
- raise Syntropy::ValidationError, 'Validation error' if clauses.empty? && !value
57
+ def validate(value, *clauses, message: 'Validation error')
58
+ raise Syntropy::ValidationError, message if clauses.empty? && !value
52
59
 
53
60
  clauses.each do |c|
54
61
  valid = param_is_valid?(value, c)
55
- raise(Syntropy::ValidationError, 'Validation error') if !valid
62
+ raise(Syntropy::ValidationError, message) if !valid
56
63
 
57
64
  value = param_convert(value, c)
58
65
  end
@@ -260,7 +260,8 @@ module Syntropy
260
260
  # @param dir [String] directory path
261
261
  # @return [Array<String>] array of file entries
262
262
  def file_search(dir)
263
- Dir[File.join(dir.gsub(/[\[\]]/) { "\\#{it}"}, '*')]
263
+ spec = File.join(dir.gsub(/[\[\]]/) { "\\#{it}"}, '{*,.*}')
264
+ Dir[spec].reject { it =~ /\/\.$/ }
264
265
  end
265
266
 
266
267
  # Computes a route entry and/or target for the given file path.
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'syntropy'
4
+ require 'syntropy/request/mock_adapter'
5
+ require 'minitest'
6
+
7
+ module Syntropy
8
+ class TestHarness
9
+ def initialize(app)
10
+ @app = app
11
+ @app.test_mode = true
12
+ end
13
+
14
+ def request(headers, body = nil)
15
+ req = mock_req(headers, body)
16
+ @app.call(req)
17
+ req
18
+ end
19
+
20
+ private
21
+
22
+ def mock_req(headers, body = nil)
23
+ Syntropy::MockAdapter.mock(headers, body)
24
+ end
25
+ end
26
+
27
+ class Request
28
+ def response_headers
29
+ adapter.response_headers
30
+ end
31
+
32
+ def response_status
33
+ adapter.status
34
+ end
35
+
36
+ def response_body
37
+ adapter.response_body
38
+ end
39
+
40
+ def response_json
41
+ raise if response_content_type != 'application/json'
42
+ JSON.parse(response_body)
43
+ end
44
+
45
+ def response_content_type
46
+ ct = response_headers['Content-Type']
47
+ return nil if !ct
48
+
49
+ m = ct.match(/^([^;]+)/)
50
+ return nil if !m
51
+
52
+ m[1]
53
+ end
54
+
55
+ def response_cookie(name)
56
+ sc = response_headers['Set-Cookie']
57
+ return nil if !sc
58
+
59
+ m = sc.match(/#{name}=([^\s]+)$/)
60
+ return nil if !m
61
+
62
+ m[1]
63
+ end
64
+ end
65
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Syntropy
4
- VERSION = '0.30.0'
4
+ VERSION = '0.31.0'
5
5
  end
data/lib/syntropy.rb CHANGED
@@ -32,25 +32,6 @@ module Syntropy
32
32
  end
33
33
  end
34
34
 
35
- def colorize(color_code)
36
- "\e[#{color_code}m#{self}\e[0m"
37
- end
38
-
39
- GREEN = "\e[32m"
40
- CLEAR = "\e[0m"
41
- YELLOW = "\e[33m"
42
-
43
- BANNER =
44
- "\n"\
45
- " #{GREEN}\n"\
46
- " #{GREEN} ooo\n"\
47
- " #{GREEN}ooooo\n"\
48
- " #{GREEN} ooo vvv #{CLEAR}Syntropy - a web framework for Ruby\n"\
49
- " #{GREEN} o vvvvv #{CLEAR}--------------------------------------\n"\
50
- " #{GREEN} #{YELLOW}|#{GREEN} vvv o #{CLEAR}https://github.com/digital-fabric/syntropy\n"\
51
- " #{GREEN} :#{YELLOW}|#{GREEN}:::#{YELLOW}|#{GREEN}::#{YELLOW}|#{GREEN}:\n"\
52
- "#{YELLOW}+++++++++++++++++++++++++++++++++++++++++++++++++++++++++\e[0m\n\n"
53
-
54
35
  class << self
55
36
  def run(env = {}, &app)
56
37
  if @in_run
@@ -63,11 +44,10 @@ module Syntropy
63
44
  begin
64
45
  @in_run = true
65
46
  machine = env[:machine] || UM.new
66
- machine.puts(env[:banner]) if env[:banner]
67
47
 
68
48
  env[:logger]&.info(message: "Running Syntropy #{Syntropy::VERSION}, UringMachine #{UM::VERSION}, Ruby #{RUBY_VERSION}")
69
49
 
70
- server = Server.new(machine, env, &app)
50
+ server = HTTP::Server.new(machine, env, &app)
71
51
 
72
52
  setup_signal_handling(machine, Fiber.current)
73
53
  server.run
data/syntropy.gemspec CHANGED
@@ -23,8 +23,7 @@ Gem::Specification.new do |s|
23
23
 
24
24
  s.add_dependency 'extralite', '~>2.14'
25
25
  s.add_dependency 'papercraft', '~>3.2.0'
26
- s.add_dependency 'uringmachine', '~>1.0.0'
27
- s.add_dependency 'cgi'
26
+ s.add_dependency 'uringmachine', '~>1.0.2'
28
27
  s.add_dependency 'escape_utils', '1.3.0'
29
28
 
30
29
  s.add_dependency 'json'
@@ -0,0 +1,3 @@
1
+ export ->(req) {
2
+ req.respond('foo')
3
+ }
data/test/helper.rb CHANGED
@@ -4,7 +4,7 @@ require 'bundler/setup'
4
4
  require_relative './coverage' if ENV['COVERAGE']
5
5
  require 'uringmachine'
6
6
  require 'syntropy'
7
- require 'syntropy/request/mock_adapter'
7
+ require 'syntropy/test'
8
8
  require 'minitest/autorun'
9
9
  require 'fileutils'
10
10
 
@@ -96,27 +96,3 @@ module Minitest::Assertions
96
96
  assert_equal exp_content_type, actual
97
97
  end
98
98
  end
99
-
100
- # Extensions to be used in conjunction with `Syntropy::TestAdapter`
101
- class Syntropy::Request
102
- def response_headers
103
- adapter.response_headers
104
- end
105
-
106
- def response_status
107
- adapter.status
108
- end
109
-
110
- def response_body
111
- adapter.response_body
112
- end
113
-
114
- def response_json
115
- raise if response_content_type != 'application/json'
116
- JSON.parse(response_body, symbolize_names: true)
117
- end
118
-
119
- def response_content_type
120
- response_headers['Content-Type']
121
- end
122
- end