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.
- checksums.yaml +4 -4
- data/LICENSE +21 -21
- data/lib/tsurezure.rb +311 -320
- data/lib/utils/error_codes.rb +24 -24
- data/lib/utils/errors.rb +65 -65
- data/lib/utils/http_utils.rb +212 -202
- data/lib/utils/logbook.rb +56 -56
- data/lib/utils/object_utils.rb +63 -63
- data/lib/utils/response.rb +95 -96
- data/nodemon.json +5 -5
- data/readme.md +9 -1
- metadata +6 -34
data/lib/tsurezure.rb
CHANGED
|
@@ -1,320 +1,311 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
require '
|
|
5
|
-
require '
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
require_relative 'utils/
|
|
9
|
-
require_relative 'utils/
|
|
10
|
-
require_relative 'utils/
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
$
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
$TRZR_PROCESS_MODE = '
|
|
22
|
-
$
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
#
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
include
|
|
37
|
-
include
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
#
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
@
|
|
166
|
-
@
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
return if @session.gets.
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
raise ErrorCodes.
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
@
|
|
187
|
-
@
|
|
188
|
-
@
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
#
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
# ----------------------------------------
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
true
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
return
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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
|