tsurezure 0.0.34 → 0.0.36

Sign up to get free protection for your applications and to get access to all the features.
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