syntropy 0.29.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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +2 -2
  3. data/CHANGELOG.md +22 -0
  4. data/README.md +0 -2
  5. data/bin/syntropy +8 -86
  6. data/cmd/_banner.rb +16 -0
  7. data/cmd/help.rb +12 -0
  8. data/cmd/serve.rb +95 -0
  9. data/cmd/test.rb +40 -0
  10. data/examples/{counter.rb → basic/counter.rb} +1 -1
  11. data/examples/{templates.rb → basic/templates.rb} +1 -1
  12. data/examples/mcp-oauth/.ruby-version +1 -0
  13. data/examples/mcp-oauth/Gemfile +8 -0
  14. data/examples/mcp-oauth/README.md +128 -0
  15. data/examples/mcp-oauth/app/.well-known/oauth-authorization-server.rb +18 -0
  16. data/examples/mcp-oauth/app/.well-known/oauth-protected-resource.rb +10 -0
  17. data/examples/mcp-oauth/app/_lib/auth_store.rb +23 -0
  18. data/examples/mcp-oauth/app/index.md +1 -0
  19. data/examples/mcp-oauth/app/mcp.rb +38 -0
  20. data/examples/mcp-oauth/app/oauth/authorize.rb +26 -0
  21. data/examples/mcp-oauth/app/oauth/consent.rb +86 -0
  22. data/examples/mcp-oauth/app/oauth/register.rb +15 -0
  23. data/examples/mcp-oauth/app/oauth/token.rb +79 -0
  24. data/examples/mcp-oauth/app/signin.rb +85 -0
  25. data/examples/mcp-oauth/test/helper.rb +9 -0
  26. data/examples/mcp-oauth/test/test_app.rb +27 -0
  27. data/examples/mcp-oauth/test/test_oauth.rb +628 -0
  28. data/lib/syntropy/app.rb +23 -12
  29. data/lib/syntropy/applets/builtin/default_error_handler.rb +3 -3
  30. data/lib/syntropy/applets/builtin/req.rb +1 -1
  31. data/lib/syntropy/dev_mode.rb +1 -1
  32. data/lib/syntropy/errors.rb +19 -12
  33. data/lib/syntropy/http/client.rb +43 -0
  34. data/lib/syntropy/http/client_connection.rb +36 -0
  35. data/lib/syntropy/http/io_extensions.rb +148 -0
  36. data/lib/syntropy/http/server.rb +174 -0
  37. data/lib/syntropy/http/server_connection.rb +367 -0
  38. data/lib/syntropy/http/status.rb +76 -0
  39. data/lib/syntropy/http.rb +7 -0
  40. data/lib/syntropy/json_api.rb +2 -5
  41. data/lib/syntropy/logger.rb +5 -1
  42. data/lib/syntropy/mime_types.rb +37 -0
  43. data/lib/syntropy/papercraft_extensions.rb +1 -1
  44. data/lib/syntropy/request/mock_adapter.rb +60 -0
  45. data/lib/syntropy/request/request_info.rb +255 -0
  46. data/lib/syntropy/request/response.rb +206 -0
  47. data/lib/syntropy/request/validation.rb +146 -0
  48. data/lib/syntropy/request.rb +99 -0
  49. data/lib/syntropy/routing_tree.rb +2 -1
  50. data/lib/syntropy/test.rb +65 -0
  51. data/lib/syntropy/utils.rb +1 -1
  52. data/lib/syntropy/version.rb +1 -1
  53. data/lib/syntropy.rb +4 -27
  54. data/syntropy.gemspec +2 -4
  55. data/test/app/.well-known/foo.rb +3 -0
  56. data/test/app/about/_error.rb +1 -1
  57. data/test/app/api+.rb +1 -1
  58. data/test/app_custom/_site.rb +1 -1
  59. data/test/bm_router_proc.rb +3 -3
  60. data/test/helper.rb +4 -27
  61. data/test/test_app.rb +83 -98
  62. data/test/test_caching.rb +2 -2
  63. data/test/test_errors.rb +6 -6
  64. data/test/test_http_client.rb +52 -0
  65. data/test/test_http_client_connection.rb +43 -0
  66. data/test/{test_connection.rb → test_http_server_connection.rb} +32 -32
  67. data/test/test_json_api.rb +14 -12
  68. data/test/test_mock_adapter.rb +59 -0
  69. data/test/{test_request_extensions.rb → test_request.rb} +150 -18
  70. data/test/test_response.rb +112 -0
  71. data/test/test_routing_tree.rb +15 -3
  72. data/test/test_server.rb +1 -1
  73. metadata +57 -35
  74. data/lib/syntropy/connection.rb +0 -402
  75. data/lib/syntropy/request_extensions.rb +0 -308
  76. data/lib/syntropy/server.rb +0 -173
  77. /data/examples/{bad.rb → basic/bad.rb} +0 -0
  78. /data/examples/{card.rb → basic/card.rb} +0 -0
  79. /data/examples/{counter.js → basic/counter.js} +0 -0
  80. /data/examples/{counter_api.rb → basic/counter_api.rb} +0 -0
  81. /data/examples/{favicon.ico → basic/favicon.ico} +0 -0
  82. /data/examples/{index.md → basic/index.md} +0 -0
@@ -1,173 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'syntropy/connection'
4
- require 'syntropy/request_extensions'
5
-
6
- module Syntropy
7
- class Server
8
- PENDING_REQUESTS_GRACE_PERIOD = 0.1
9
- PENDING_REQUESTS_TIMEOUT_PERIOD = 5
10
-
11
- def self.syntropy_app(_machine, env)
12
- if env[:app_location]
13
- env[:logger]&.info(message: 'Loading web app', location: env[:app_location])
14
- require env[:app_location]
15
-
16
- env.merge!(Syntropy.config)
17
- end
18
- env[:app]
19
- end
20
-
21
- def self.static_app(env); end
22
-
23
- def initialize(machine, env, &app)
24
- @machine = machine
25
- @env = env
26
- @app = app || app_from_env
27
- @server_fds = []
28
- @accept_fibers = []
29
- end
30
-
31
- def app_from_env
32
- case @env[:app_type]
33
- when nil, :syntropy
34
- Server.syntropy_app(@machine, @env)
35
- when :static
36
- Server.static_app(@env)
37
- else
38
- raise "Invalid app type #{@env[:app_type].inspect}"
39
- end
40
- end
41
-
42
- def run
43
- setup
44
- @machine.await(@accept_fibers)
45
- rescue UM::Terminate
46
- graceful_shutdown
47
- end
48
-
49
- def stop!
50
- graceful_shutdown
51
- end
52
-
53
- private
54
-
55
- def setup
56
- bind_info = get_bind_entries
57
- bind_info.each do |(host, port)|
58
- fd = setup_server_socket(host, port)
59
- @server_fds << fd
60
- @accept_fibers << @machine.spin { accept_incoming(fd) }
61
- end
62
- bind_string = bind_info.map { it.join(':') }.join(', ')
63
- @env[:logger]&.info(message: "Listening on #{bind_string}")
64
- setup_server_extensions
65
-
66
- # map fibers
67
- @connection_fibers = Set.new
68
- end
69
-
70
- def get_bind_entries
71
- bind = @env[:bind]
72
- case bind
73
- when Array
74
- bind.map { bind_info(it) }
75
- when String
76
- [bind_info(bind)]
77
- else
78
- # default
79
- [['0.0.0.0', 1234]]
80
- end
81
- end
82
-
83
- def bind_info(bind_string)
84
- parts = bind_string.split(':')
85
- [parts[0], parts[1].to_i]
86
- end
87
-
88
- def setup_server_socket(host, port)
89
- fd = @machine.socket(UM::AF_INET, UM::SOCK_STREAM, 0, 0)
90
- @machine.setsockopt(fd, UM::SOL_SOCKET, UM::SO_REUSEADDR, true)
91
- @machine.setsockopt(fd, UM::SOL_SOCKET, UM::SO_REUSEPORT, true)
92
- @machine.bind(fd, host, port)
93
- @machine.listen(fd, UM::SOMAXCONN)
94
- fd
95
- end
96
-
97
- def setup_server_extensions
98
- extensions = @env[:server_extensions]
99
- return if !extensions
100
-
101
- server_name = extensions[:name]
102
- if extensions[:date]
103
- @date_header_fiber = @machine.spin {
104
- @machine.periodically(1) { update_server_headers(server_name) }
105
- }
106
- update_server_headers(server_name)
107
- elsif server_name
108
- @env[:server_headers] = "Server: #{server_name}\r\n"
109
- end
110
- end
111
-
112
- def update_server_headers(server_name)
113
- @env[:server_date] = Time.now
114
- if server_name
115
- @env[:server_headers] = "Server: #{server_name}\r\nDate: #{@env[:server_date].httpdate}\r\n"
116
- else
117
- @env[:server_headers] = "Date: #{Time.now.httpdate}\r\n"
118
- end
119
- end
120
-
121
- def accept_incoming(listen_fd)
122
- @machine.accept_each(listen_fd) { start_client_connection(it) }
123
- rescue UM::Terminate
124
- # terminated
125
- end
126
-
127
- def start_client_connection(fd)
128
- conn = Connection.new(self, @machine, fd, @env, &@app)
129
- f = @machine.spin(conn) do
130
- it.run
131
- ensure
132
- @connection_fibers.delete(f)
133
- end
134
- @connection_fibers << f
135
- end
136
-
137
- def close_all_server_fds
138
- @server_fds.each { @machine.close_async(it) }
139
- end
140
-
141
- STOP = UM::Terminate.new
142
-
143
- def stop_accept_fibers
144
- @accept_fibers.each { @machine.schedule(it, STOP) if !it.done? }
145
- @machine.await(@accept_fibers)
146
- end
147
-
148
- def graceful_shutdown
149
- @env[:logger]&.info(message: 'Shutting down gracefully...')
150
-
151
- # stop listening
152
- close_all_server_fds
153
- stop_accept_fibers
154
- @machine.snooze
155
-
156
- return if @connection_fibers.empty?
157
-
158
- # sleep for a bit, let requests finish
159
- @machine.sleep(PENDING_REQUESTS_GRACE_PERIOD)
160
- return if @connection_fibers.empty?
161
-
162
- # terminate pending fibers
163
- pending = @connection_fibers.to_a
164
- pending.each { @machine.schedule(it, STOP) }
165
-
166
- @machine.timeout(PENDING_REQUESTS_TIMEOUT_PERIOD, UM::Terminate) do
167
- @machine.await(@connection_fibers)
168
- rescue UM::Terminate
169
- # timeout on waiting for adapters to finish running, do nothing
170
- end
171
- end
172
- end
173
- end
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes