tsurezure 0.0.34 → 0.0.36

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/lib/tsurezure.rb CHANGED
@@ -1,320 +1,311 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require 'socket'
5
- require 'json'
6
- require 'pry'
7
-
8
- require_relative 'utils/http_utils' # mainly used to create http responses.
9
- require_relative 'utils/object_utils' # various object validation utilities
10
- require_relative 'utils/error_codes' # for generating errors.
11
- require_relative 'utils/response' # handles request and generates responses.
12
-
13
- $TRZR_PROCESS_MODE = nil
14
- $TRZR_LOG = true
15
- TRZR_STARTED_AT = Time.now.to_i
16
-
17
- INVALID_RESPONSE_FORMAT = "if responding from a middleware, \
18
- you must return a hash that includes a :message property."
19
-
20
- ARGV.each do |arg|
21
- $TRZR_PROCESS_MODE = 'development' if arg == '--development'
22
- $TRZR_PROCESS_MODE = 'production' if arg == '--production'
23
- $TRZR_LOG = false if arg == '--silent'
24
- end
25
-
26
- $TRZR_PROCESS_MODE = 'production' if $TRZR_PROCESS_MODE.nil?
27
-
28
- # main class for tsurezure.
29
- class Tsurezure
30
- include OUtil
31
-
32
- ##
33
- # this class is made to handle requests coming from
34
- # a single client on a tcp server.
35
- class RequestHandler
36
- include HTTPUtils
37
- include ErrorCodes
38
- include TResponse
39
-
40
- ##
41
- # initializes with a client socket returned from a +TCPSocket+ object.
42
-
43
- def initialize(session)
44
- @session = session
45
- # endpoints are organized into arrays, sorted by method.
46
- # ex: { get: [ ...endpoint objects ], post: [ ... ] }
47
- # etc.
48
- end
49
-
50
- ##
51
- # handles an incoming request from the open socket in +@session+.
52
- # constructs a formatted request object from the original request object,
53
- # and calls +send_final_response+ in order to send a response.
54
- def handle_request(request)
55
- url_main = request[:url].split('?')[0]
56
-
57
- request_object = {
58
- method: request[:method],
59
- url: url_main,
60
- params: HTTPUtils::URLUtils.extract_url_params(request[:url]),
61
- protocol: request[:protocol],
62
- headers: request[:headers],
63
- data: request[:data]
64
- }
65
-
66
- generate_response request_object
67
- end
68
-
69
- ##
70
- # generate a response from a supplied request object
71
- # from +handle_request+.
72
- def generate_response(request_object)
73
- type = 'text/plain'
74
-
75
- unless request_object[:options].nil? || request_object[:options].empty?
76
- type = request_object[:options][:content_type]
77
- end
78
-
79
- res = TResponse.get_response request_object, @endpoints
80
-
81
- # to initialize: session and length of response
82
- responder = HTTPUtils::ServerResponse.new(
83
- @session,
84
- res[:message].nil? ? '' : res[:message].bytesize
85
- )
86
-
87
- go_through_middleware request_object, responder, res, type
88
- end
89
-
90
- def get_correct_middleware(request_object)
91
- @middleware.keys.select do |pat|
92
- HTTPUtils::URLUtils.matches_url_regex?(pat, request_object[:url]) ||
93
- pat == '*'
94
- end
95
- end
96
-
97
- def fix_req(request, mid)
98
- request[:vars] =
99
- HTTPUtils::URLUtils.get_match_indices(
100
- mid[:path_regex],
101
- request[:url]
102
- ) || {}
103
-
104
- request[:options] = mid[:options]
105
-
106
- request
107
- end
108
-
109
- def respond_with_error(error)
110
- Logbook::Dev.log(error)
111
-
112
- message = { error: error }.to_json
113
-
114
- responder = HTTPUtils::ServerResponse.new(
115
- @session,
116
- message.bytesize
117
- )
118
-
119
- responder.respond message, {}, 500, 'application/json'
120
- end
121
-
122
- def send_middleware_response(req, resp, type)
123
- res = resp.merge req
124
-
125
- return respond_with_error INVALID_RESPONSE_FORMAT if res[:message].nil?
126
-
127
- responder = HTTPUtils::ServerResponse.new(
128
- @session,
129
- res[:message].bytesize
130
- )
131
-
132
- responder.respond res[:message], res[:options] || {}, res[:status], type
133
- end
134
-
135
- def call_each_middleware(request, middleware, type)
136
- alt = nil
137
-
138
- middleware.each do |path|
139
- break if alt
140
-
141
- @middleware[path]&.each do |mid|
142
- alt = mid[:callback].call fix_req(request, mid)
143
- end
144
- end
145
-
146
- return true unless alt
147
-
148
- send_middleware_response(request, alt, type)
149
- end
150
-
151
- def go_through_middleware(request_object, responder, res, type)
152
- exp = get_correct_middleware request_object
153
-
154
- done = call_each_middleware request_object, exp, type
155
-
156
- return unless done
157
-
158
- # to send: response, options, status, content_type
159
- responder.respond res[:message], res[:options] || {}, res[:status], type
160
- end
161
-
162
- ##
163
- # main process, allows server to handle requests
164
- def process(client, endpoints, middleware)
165
- @endpoints = endpoints
166
- @middleware = middleware
167
- @request = client.gets
168
- # wait until server isn't recieving anything
169
- return if @session.gets.nil?
170
- return if @session.gets.chop.length.zero?
171
-
172
- request_made = HTTPUtils.make_proper_request client, @request
173
-
174
- request_to_handle = HTTPUtils.make_request_object request_made
175
-
176
- handle_request request_to_handle
177
- end
178
- end
179
-
180
- ##
181
- # prepares the server to run on a specified port.
182
- def initialize(port)
183
- raise ErrorCodes.nan_error 'port' unless port.is_a? Numeric
184
- raise ErrorCodes.range_error 0, 65_535 unless (0..65_535).include? port
185
-
186
- @server = TCPServer.new port
187
- @port = port
188
- @endpoints = {}
189
- @middleware = {}
190
- end
191
-
192
- attr_reader :endpoints # access endpoints object from outside scope
193
-
194
- def add_middleware(path, callback, options)
195
- unless path.is_a? String
196
- raise ArgumentError, 'first argument to middleware\
197
- must be string or function.'
198
- end
199
-
200
- middleware_object = {
201
- options: options, callback: callback, path_regex: path
202
- }
203
-
204
- @middleware[path] << middleware_object if @middleware[path]
205
-
206
- @middleware[path] = [middleware_object] unless @middleware[path]
207
- end
208
-
209
- def register(http_method, path, callback, options = nil)
210
- http_method = http_method.upcase
211
- insurance = ensure_registration http_method, path, callback, options
212
-
213
- raise ArgumentError, insurance if insurance.class == String
214
-
215
- # register a new endpoint but do not register dupes
216
- @endpoints[http_method] = {} unless @endpoints.key? http_method
217
-
218
- new_endpoint = { path: path, responder: callback, options: options }
219
-
220
- add_new_endpoint new_endpoint, http_method
221
- end
222
-
223
- ##
224
- # run when the server is prepared to accept requests.
225
- def listen(callback = nil)
226
- if $TRZR_PROCESS_MODE == 'development'
227
- puts "[trzr_dev] running on port #{@port}!"
228
- end
229
-
230
- # call the callback if there's one provided
231
- callback.call server_opts if callback.is_a? Proc
232
-
233
- # create a new thread for handle each incoming request
234
- loop do
235
- Thread.start(@server.accept) do |client|
236
- RequestHandler.new(client).process client, @endpoints, @middleware
237
- end
238
- end
239
- end
240
-
241
- def kill
242
- abort
243
- end
244
-
245
- private
246
-
247
- def server_opts
248
- {
249
- port: @port,
250
- endpoints: @endpoints,
251
- middleware: @middleware
252
- }
253
- end
254
-
255
- # ----------------------------------------
256
- # :section: registration of endpoints and
257
- # all endpoint management methods follow.
258
- # ----------------------------------------
259
-
260
- def ensure_registration(*args)
261
- verification = verify_registration(*args)
262
-
263
- return verification unless verification
264
-
265
- verification # to register
266
- end
267
-
268
- def validate_registration_params(method, path, responder)
269
- unless TResponse::Utils.new.valid_methods.include? method
270
- return "#{method} is not a valid http method."
271
- end
272
-
273
- return 'invalid path type. must be a string.' unless path.class == String
274
-
275
- if path.empty? || path.chr != '/'
276
- return 'invalid path. must begin with "/".'
277
- end
278
-
279
- return 'invalid responder type. must a proc.' unless responder.class == Proc
280
-
281
- true
282
- end
283
-
284
- def verify_registration(http_method, path, responder, options)
285
- valid = validate_registration_params http_method, path, responder
286
-
287
- return valid unless valid == true
288
- return true if options.nil? || options.empty?
289
- return 'invalid options type.' unless options.class == Hash
290
-
291
- valid_opts = %w[content_type method location]
292
-
293
- opts_valid = OUtil.check_against_array(options.keys, valid_opts, 'register')
294
-
295
- return 'invalid options provided to register.' unless opts_valid
296
-
297
- true # to ensure_registration
298
- end
299
-
300
- def add_new_endpoint(endpoint, method)
301
- @endpoints[method].each do |_, value|
302
- if value[:path] == endpoint[:path]
303
- raise ArgumentError, 'cannot register duplicate path.'
304
- end
305
- end
306
-
307
- # add endpoint to list of registered endpoints
308
- @endpoints[method][endpoint[:path]] = endpoint
309
- end
310
- end
311
-
312
- at_exit do
313
- if $TRZR_PROCESS_MODE == 'development' && $TRZR_LOG == true
314
- time = Time.now.to_i - TRZR_STARTED_AT
315
- puts
316
- puts '[trzr_dev] shutting down. goodbye...'
317
- puts "[trzr_dev] shut down after #{Time.at(time).utc.strftime('%H:%M:%S')}."
318
- puts
319
- end
320
- end
1
+ # frozen_string_literal: true
2
+
3
+ require 'socket'
4
+ require 'json'
5
+ require 'pry'
6
+
7
+ require_relative 'utils/http_utils' # mainly used to create http responses.
8
+ require_relative 'utils/object_utils' # various object validation utilities
9
+ require_relative 'utils/error_codes' # for generating errors.
10
+ require_relative 'utils/response' # handles request and generates responses.
11
+
12
+ $TRZR_PROCESS_MODE = nil
13
+ $TRZR_LOG = true
14
+ TRZR_STARTED_AT = Time.now.to_i
15
+
16
+ INVALID_RESPONSE_FORMAT = "if responding from a middleware, \
17
+ you must return a hash that includes a :message property."
18
+
19
+ ARGV.each do |arg|
20
+ $TRZR_PROCESS_MODE = 'development' if arg == '--development'
21
+ $TRZR_PROCESS_MODE = 'production' if arg == '--production'
22
+ $TRZR_LOG = false if arg == '--silent'
23
+ end
24
+
25
+ $TRZR_PROCESS_MODE = 'production' if $TRZR_PROCESS_MODE.nil?
26
+
27
+ # main class for tsurezure.
28
+ class Tsurezure
29
+ include OUtil
30
+
31
+ ##
32
+ # this class is made to handle requests coming from
33
+ # a single client on a tcp server.
34
+ class RequestHandler
35
+ include HTTPUtils
36
+ include ErrorCodes
37
+ include TResponse
38
+
39
+ ##
40
+ # initializes with a client socket returned from a +TCPSocket+ object.
41
+
42
+ def initialize(session)
43
+ @session = session
44
+ # endpoints are organized into arrays, sorted by method.
45
+ # ex: { get: [ ...endpoint objects ], post: [ ... ] }
46
+ # etc.
47
+ end
48
+
49
+ ##
50
+ # handles an incoming request from the open socket in +@session+.
51
+ # constructs a formatted request object from the original request object,
52
+ # and calls +send_final_response+ in order to send a response.
53
+ def handle_request(request)
54
+ url_main = request[:url].split('?')[0]
55
+
56
+ request_object = {
57
+ method: request[:method],
58
+ url: url_main,
59
+ params: HTTPUtils::URLUtils.extract_url_params(request[:url]),
60
+ protocol: request[:protocol],
61
+ headers: request[:headers],
62
+ data: request[:data]
63
+ }
64
+
65
+ generate_response request_object
66
+ end
67
+
68
+ ##
69
+ # generate a response from a supplied request object
70
+ # from +handle_request+.
71
+ def generate_response(request_object)
72
+ type = 'text/plain'
73
+
74
+ unless request_object[:options].nil? || request_object[:options].empty?
75
+ type = request_object[:options][:content_type]
76
+ end
77
+
78
+ res = TResponse.get_response request_object, @endpoints
79
+
80
+ # to initialize: session and length of response
81
+ responder = HTTPUtils::ServerResponse.new(
82
+ @session,
83
+ res[:message].nil? ? '' : res[:message].bytesize
84
+ )
85
+
86
+ go_through_middleware request_object, responder, res, type
87
+ end
88
+
89
+ def get_correct_middleware(request_object)
90
+ @middleware.keys.select do |pat|
91
+ HTTPUtils::URLUtils.matches_url_regex?(pat, request_object[:url]) ||
92
+ pat == '*'
93
+ end
94
+ end
95
+
96
+ def fix_req(request, mid)
97
+ request[:vars] =
98
+ HTTPUtils::URLUtils.get_match_indices(
99
+ mid[:path_regex],
100
+ request[:url]
101
+ ) || {}
102
+
103
+ request[:options] = mid[:options]
104
+
105
+ request
106
+ end
107
+
108
+ def respond_with_error(error)
109
+ Logbook::Dev.log(error)
110
+
111
+ message = { error: error }.to_json
112
+
113
+ responder = HTTPUtils::ServerResponse.new(
114
+ @session,
115
+ message.bytesize
116
+ )
117
+
118
+ responder.respond message, {}, 500, 'application/json'
119
+ end
120
+
121
+ def send_middleware_response(req, resp, type)
122
+ res = resp.merge req
123
+
124
+ return respond_with_error INVALID_RESPONSE_FORMAT if res[:message].nil?
125
+
126
+ responder = HTTPUtils::ServerResponse.new(
127
+ @session,
128
+ res[:message].bytesize
129
+ )
130
+
131
+ responder.respond res[:message], res[:options] || {}, res[:status], type
132
+ end
133
+
134
+ def call_each_middleware(request, middleware, type)
135
+ alt = nil
136
+
137
+ middleware.each do |path|
138
+ break if alt
139
+
140
+ @middleware[path]&.each do |mid|
141
+ alt = mid[:callback].call fix_req(request, mid)
142
+ end
143
+ end
144
+
145
+ return true unless alt
146
+
147
+ send_middleware_response(request, alt, type)
148
+ end
149
+
150
+ def go_through_middleware(request_object, responder, res, type)
151
+ exp = get_correct_middleware request_object
152
+
153
+ done = call_each_middleware request_object, exp, type
154
+
155
+ return unless done
156
+
157
+ # to send: response, options, status, content_type
158
+ responder.respond res[:message], res[:options] || {}, res[:status], type
159
+ end
160
+
161
+ ##
162
+ # main process, allows server to handle requests
163
+ def process(client, endpoints, middleware)
164
+ @endpoints = endpoints
165
+ @middleware = middleware
166
+ @request = client.gets
167
+ # wait until server isn't recieving anything
168
+ return if @session.gets.nil?
169
+ return if @session.gets.chop.length.zero?
170
+
171
+ request_made = HTTPUtils.make_proper_request client, @request
172
+
173
+ request_to_handle = HTTPUtils.make_request_object request_made
174
+
175
+ handle_request request_to_handle
176
+ end
177
+ end
178
+
179
+ ##
180
+ # prepares the server to run on a specified port.
181
+ def initialize(port)
182
+ raise ErrorCodes.nan_error 'port' unless port.is_a? Numeric
183
+ raise ErrorCodes.range_error 0, 65_535 unless (0..65_535).include? port
184
+
185
+ @server = TCPServer.new port
186
+ @port = port
187
+ @endpoints = {}
188
+ @middleware = {}
189
+ end
190
+
191
+ attr_reader :endpoints # access endpoints object from outside scope
192
+
193
+ def add_middleware(path, callback, options)
194
+ unless path.is_a? String
195
+ raise ArgumentError, 'first argument to middleware\
196
+ must be string or function.'
197
+ end
198
+
199
+ middleware_object = {
200
+ options: options, callback: callback, path_regex: path
201
+ }
202
+
203
+ @middleware[path] << middleware_object if @middleware[path]
204
+
205
+ @middleware[path] = [middleware_object] unless @middleware[path]
206
+ end
207
+
208
+ def register(http_method, path, callback, options = nil)
209
+ http_method = http_method.upcase
210
+ insurance = ensure_registration http_method, path, callback, options
211
+
212
+ raise ArgumentError, insurance if insurance.instance_of? String
213
+
214
+ # register a new endpoint but do not register dupes
215
+ @endpoints[http_method] = {} unless @endpoints.key? http_method
216
+
217
+ new_endpoint = { path: path, responder: callback, options: options }
218
+
219
+ add_new_endpoint new_endpoint, http_method
220
+ end
221
+
222
+ ##
223
+ # run when the server is prepared to accept requests.
224
+ def listen(callback = nil)
225
+ puts "[trzr_dev] running on port #{@port}!" if $TRZR_PROCESS_MODE == 'development'
226
+
227
+ # call the callback if there's one provided
228
+ callback.call server_opts if callback.is_a? Proc
229
+
230
+ # create a new thread for handle each incoming request
231
+ loop do
232
+ Thread.start(@server.accept) do |client|
233
+ RequestHandler.new(client).process client, @endpoints, @middleware
234
+ end
235
+ end
236
+ end
237
+
238
+ def kill
239
+ abort
240
+ end
241
+
242
+ private
243
+
244
+ def server_opts
245
+ {
246
+ port: @port,
247
+ endpoints: @endpoints,
248
+ middleware: @middleware
249
+ }
250
+ end
251
+
252
+ # ----------------------------------------
253
+ # :section: registration of endpoints and
254
+ # all endpoint management methods follow.
255
+ # ----------------------------------------
256
+
257
+ def ensure_registration(*args)
258
+ verification = verify_registration(*args)
259
+
260
+ return verification unless verification
261
+
262
+ verification # to register
263
+ end
264
+
265
+ def validate_registration_params(method, path, responder)
266
+ return "#{method} is not a valid http method." unless TResponse::Utils.new.valid_methods.include? method
267
+
268
+ return 'invalid path type. must be a string.' unless path.instance_of?(String)
269
+
270
+ return 'invalid path. must begin with "/".' if path.empty? || path.chr != '/'
271
+
272
+ return 'invalid responder type. must a proc.' unless responder.instance_of?(Proc)
273
+
274
+ true
275
+ end
276
+
277
+ def verify_registration(http_method, path, responder, options)
278
+ valid = validate_registration_params http_method, path, responder
279
+
280
+ return valid unless valid == true
281
+ return true if options.nil? || options.empty?
282
+ return 'invalid options type.' unless options.instance_of?(Hash)
283
+
284
+ valid_opts = %w[content_type method location]
285
+
286
+ opts_valid = OUtil.check_against_array(options.keys, valid_opts, 'register')
287
+
288
+ return 'invalid options provided to register.' unless opts_valid
289
+
290
+ true # to ensure_registration
291
+ end
292
+
293
+ def add_new_endpoint(endpoint, method)
294
+ @endpoints[method].each do |_, value|
295
+ raise ArgumentError, 'cannot register duplicate path.' if value[:path] == endpoint[:path]
296
+ end
297
+
298
+ # add endpoint to list of registered endpoints
299
+ @endpoints[method][endpoint[:path]] = endpoint
300
+ end
301
+ end
302
+
303
+ at_exit do
304
+ if $TRZR_PROCESS_MODE == 'development' && $TRZR_LOG == true
305
+ time = Time.now.to_i - TRZR_STARTED_AT
306
+ puts
307
+ puts '[trzr_dev] shutting down. goodbye...'
308
+ puts "[trzr_dev] shut down after #{Time.at(time).utc.strftime('%H:%M:%S')}."
309
+ puts
310
+ end
311
+ end