blest 1.0.0 → 1.0.2

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 (5) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/README.md +5 -40
  4. data/lib/blest.rb +3 -344
  5. metadata +3 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c4f44d941bb783457e67fa52ada69792f5f645a76bac399165d59c73f4ba7b4b
4
- data.tar.gz: 8f7ee45d396ae4cab3c8178f1bef7e6605612efb94e259cbb35bcd3da842e347
3
+ metadata.gz: eb15eb80f38f62587b534d5ce5e5a81a0a8995050033745b2f100fede9557794
4
+ data.tar.gz: b7e758f3d3396af0f14952b3512c5e2550f7a35089add6aaf479994bb4ed778a
5
5
  SHA512:
6
- metadata.gz: aecfa3005a9a29bcfd4b4f501710a0f8a49251ff7fc62f4043bfe8e5b0e7f6c8e2ddbb09df2ae14c08bc7617a09ab08d84ed52d9023272bd9bb05dabd1fb8e41
7
- data.tar.gz: 2855b895975b23f418e0e084ecd3d15bd8df43764a896aca1f8fa5788d21e74ab7fb5ff5fdc4bbcd79a074dff7ce2f14bb3a10ee4e497d8ef1d63b1a645066c3
6
+ metadata.gz: 3c18640e749fb37f65d380ac18966002662b652081bafb908e9e7a1771dbae30348272888ae60bdf4f254fefeec6f4c2c822b2fc47341a4469312413c03f45d9
7
+ data.tar.gz: 731fedd0800a8e82a9ab3d7bd07e1bd6b29b88145ce8d43054f7d52b2519a6e46f8472273c8bfa85b5aaf7451d6821b19c698a3f97319b823b7c78947a47bbfd
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2023 JHunt
3
+ Copyright (c) 2023-2024 JHunt
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -24,39 +24,9 @@ gem install blest
24
24
 
25
25
  ## Usage
26
26
 
27
- The `Blest` class of this library has an interface similar to Sinatra. It also provides a `Router` class with a `handle` method for use in an existing Ruby API and an `HttpClient` class with a `request` method for making BLEST HTTP requests.
28
-
29
- ```ruby
30
- require 'blest'
31
-
32
- app = Blest.new(timeout: 1000, port: 8080, host: 'localhost', cors: 'http://localhost:3000')
33
-
34
- # Create some middleware (optional)
35
- app.before do |body, context|
36
- if context.dig('headers', 'auth') == 'myToken'?
37
- context['user'] = {
38
- # user info for example
39
- }
40
- nil
41
- else
42
- raise RuntimeError, "Unauthorized"
43
- end
44
- end
45
-
46
- # Create a route controller
47
- app.route('greet') do |body, context|
48
- {
49
- greeting: "Hi, #{body['name']}!"
50
- }
51
- end
52
-
53
- # Start the server
54
- app.listen
55
- ```
56
-
57
27
  ### Router
58
28
 
59
- The following example uses Sinatra.
29
+ The following example uses Sinatra, but you can find examples with other frameworks [here](examples).
60
30
 
61
31
  ```ruby
62
32
  require 'sinatra'
@@ -68,14 +38,9 @@ router = Router.new(timeout: 1000)
68
38
 
69
39
  # Create some middleware (optional)
70
40
  router.before do |body, context|
71
- if context.dig('headers', 'auth') == 'myToken'?
72
- context['user'] = {
73
- # user info for example
74
- }
75
- nil
76
- else
77
- raise RuntimeError, "Unauthorized"
78
- end
41
+ context['user'] = {
42
+ # user info for example
43
+ }
79
44
  end
80
45
 
81
46
  # Create a route controller
@@ -111,7 +76,7 @@ client = HttpClient.new('http://localhost:8080', max_batch_size = 25, buffer_del
111
76
 
112
77
  # Send a request
113
78
  begin
114
- result = client.request('greet', { 'name': 'Steve' }, ['greeting']).value
79
+ result = client.request('greet', { 'name': 'Steve' }).value
115
80
  # Do something with the result
116
81
  rescue => error
117
82
  # Do something in case of error
data/lib/blest.rb CHANGED
@@ -1,4 +1,3 @@
1
- require 'socket'
2
1
  require 'json'
3
2
  require 'concurrent'
4
3
  require 'securerandom'
@@ -148,29 +147,6 @@ end
148
147
 
149
148
 
150
149
 
151
- class Blest < Router
152
- @options = nil
153
- @errorhandler = nil
154
-
155
- def initialize(options = nil)
156
- @options = options
157
- super(options)
158
- end
159
-
160
- def errorhandler(&errorhandler)
161
- @errorhandler = errorhandler
162
- puts 'The errorhandler method is not currently used'
163
- end
164
-
165
- def listen(*args)
166
- request_handler = create_request_handler(@routes)
167
- server = create_http_server(request_handler, @options)
168
- server.call(*args)
169
- end
170
- end
171
-
172
-
173
-
174
150
  class HttpClient
175
151
  attr_reader :queue, :futures
176
152
  attr_accessor :url, :max_batch_size, :buffer_delay, :headers
@@ -279,324 +255,6 @@ end
279
255
 
280
256
 
281
257
 
282
- class HttpServer
283
- attr_reader :url, :host, :port, :cors, :headers
284
-
285
- def initialize(request_handler, options = {})
286
- unless request_handler.is_a?(Router)
287
- raise ArgumentError, "request_handler must be an instance of Router class"
288
- end
289
- @request_handler = request_handler
290
- if options
291
- options_error = validate_server_options(options)
292
- raise ArgumentError, options_error if options_error
293
- else
294
- @options = {}
295
- end
296
-
297
- @url = options && get_value(options, :url) || '/'
298
- @host = options && get_value(options, :host) || 'localhost'
299
- @port = options && get_value(options, :port) || 8080
300
- @cors = options && get_value(options, :cors) || false
301
- cors_default = cors == true ? '*' : cors || ''
302
-
303
- @headers = {
304
- 'access-control-allow-origin' => options && get_value(options, :accessControlAllowOrigin) || cors_default,
305
- 'content-security-policy' => options && get_value(options, :contentSecurityPolicy) || "default-src 'self';base-uri 'self';font-src 'self' https: data:;form-action 'self';frame-ancestors 'self';img-src 'self' data:;object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline';upgrade-insecure-requests",
306
- 'cross-origin-opener-policy' => options && get_value(options, :crossOriginOpenerPolicy) || 'same-origin',
307
- 'cross-origin-resource-policy' => options && get_value(options, :crossOriginResourcePolicy) || 'same-origin',
308
- 'origin-agent-cluster' => options && get_value(options, :originAgentCluster) || '?1',
309
- 'referrer-policy' => options && get_value(options, :referrerPolicy) || 'no-referrer',
310
- 'strict-transport-security' => options && get_value(options, :strictTransportSecurity) || 'max-age=1555200 includeSubDomains',
311
- 'x-content-type-options' => options && get_value(options, :xContentTypeOptions) || 'nosniff',
312
- 'x-dns-prefetch-control' => options && get_value(options, :xDnsPrefetchOptions) || 'off',
313
- 'x-download-options' => options && get_value(options, :xDownloadOptions) || 'noopen',
314
- 'x-frame-options' => options && get_value(options, :xFrameOptions) || 'SAMEORIGIN',
315
- 'x-permitted-cross-domain-policies' => options && get_value(options, :xPermittedCrossDomainPolicies) || 'none',
316
- 'x-xss-protection' => options && get_value(options, :xXssProtection) || '0'
317
- }
318
- end
319
-
320
- def listen()
321
- server = TCPServer.new(@host, @port)
322
- puts "Server listening on port #{@port}"
323
-
324
- loop do
325
- client = server.accept
326
- Thread.new { handle_http_request(client, @request_handler, @http_headers) }
327
- end
328
- end
329
-
330
- private
331
-
332
- def parse_http_request(request_line)
333
- method, path, _ = request_line.split(' ')
334
- { method: method, path: path }
335
- end
336
-
337
- def parse_http_headers(client)
338
- headers = {}
339
-
340
- while (line = client.gets.chomp)
341
- break if line.empty?
342
-
343
- key, value = line.split(':', 2)
344
- headers[key] = value.strip
345
- end
346
-
347
- headers
348
- end
349
-
350
- def parse_post_body(client, content_length)
351
- body = ''
352
-
353
- while content_length > 0
354
- chunk = client.readpartial([content_length, 4096].min)
355
- body += chunk
356
- content_length -= chunk.length
357
- end
358
-
359
- body
360
- end
361
-
362
- def build_http_response(status, headers, body)
363
- response = "HTTP/1.1 #{status}\r\n"
364
- headers.each { |key, value| response += "#{key}: #{value}\r\n" }
365
- response += "\r\n"
366
- response += body
367
- response
368
- end
369
-
370
- def handle_http_request(client, request_handler, http_headers)
371
- request_line = client.gets
372
- return unless request_line
373
-
374
- request = parse_http_request(request_line)
375
-
376
- headers = parse_http_headers(client)
377
-
378
- if request[:path] != '/'
379
- response = build_http_response('404 Not Found', http_headers, '')
380
- client.print(response)
381
- elsif request[:method] == 'OPTIONS'
382
- response = build_http_response('204 No Content', http_headers, '')
383
- client.print(response)
384
- elsif request[:method] == 'POST'
385
-
386
- content_length = headers['Content-Length'].to_i
387
- body = parse_post_body(client, content_length)
388
-
389
- begin
390
- json_data = JSON.parse(body)
391
- context = {
392
- 'headers' => headers
393
- }
394
-
395
- response_headers = http_headers.merge({
396
- 'Content-Type' => 'application/json'
397
- })
398
-
399
- result, error = request_handler.(json_data, context)
400
-
401
- if error
402
- response_json = error.to_json
403
- response = build_http_response('500 Internal Server Error', response_headers, response_json)
404
- client.print response
405
- elsif result
406
- response_json = result.to_json
407
- response = build_http_response('200 OK', response_headers, response_json)
408
- client.print response
409
- else
410
- response = build_http_response('500 Internal Server Error', response_headers, { 'message' => 'Request handler failed to return a result' }.to_json)
411
- client.print response
412
- end
413
-
414
- rescue JSON::ParserError
415
- response = build_http_response('400 Bad Request', http_headers, '')
416
- end
417
-
418
- else
419
- response = build_http_response('405 Method Not Allowed', http_headers, '')
420
- client.print(response)
421
- end
422
- client.close()
423
- end
424
-
425
- end
426
-
427
-
428
-
429
-
430
-
431
-
432
-
433
- def create_http_server(request_handler, options = nil)
434
- if options
435
- options_error = validate_server_options(options)
436
- raise ArgumentError, options_error if options_error
437
- end
438
-
439
- url = options && get_value(options, :url) || '/'
440
- port = options && get_value(options, :port) || 8080
441
- cors = options && get_value(options, :cors) || false
442
- cors_default = cors == true ? '*' : cors || ''
443
-
444
- http_headers = {
445
- 'access-control-allow-origin' => options && get_value(options, :accessControlAllowOrigin) || cors_default,
446
- 'content-security-policy' => options && get_value(options, :contentSecurityPolicy) || "default-src 'self';base-uri 'self';font-src 'self' https: data:;form-action 'self';frame-ancestors 'self';img-src 'self' data:;object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline';upgrade-insecure-requests",
447
- 'cross-origin-opener-policy' => options && get_value(options, :crossOriginOpenerPolicy) || 'same-origin',
448
- 'cross-origin-resource-policy' => options && get_value(options, :crossOriginResourcePolicy) || 'same-origin',
449
- 'origin-agent-cluster' => options && get_value(options, :originAgentCluster) || '?1',
450
- 'referrer-policy' => options && get_value(options, :referrerPolicy) || 'no-referrer',
451
- 'strict-transport-security' => options && get_value(options, :strictTransportSecurity) || 'max-age=1555200 includeSubDomains',
452
- 'x-content-type-options' => options && get_value(options, :xContentTypeOptions) || 'nosniff',
453
- 'x-dns-prefetch-control' => options && get_value(options, :xDnsPrefetchOptions) || 'off',
454
- 'x-download-options' => options && get_value(options, :xDownloadOptions) || 'noopen',
455
- 'x-frame-options' => options && get_value(options, :xFrameOptions) || 'SAMEORIGIN',
456
- 'x-permitted-cross-domain-policies' => options && get_value(options, :xPermittedCrossDomainPolicies) || 'none',
457
- 'x-xss-protection' => options && get_value(options, :xXssProtection) || '0'
458
- }
459
-
460
- def parse_http_request(request_line)
461
- method, path, _ = request_line.split(' ')
462
- { method: method, path: path }
463
- end
464
-
465
- def parse_http_headers(client)
466
- headers = {}
467
-
468
- while (line = client.gets.chomp)
469
- break if line.empty?
470
-
471
- key, value = line.split(':', 2)
472
- headers[key] = value.strip
473
- end
474
-
475
- headers
476
- end
477
-
478
- def parse_post_body(client, content_length)
479
- body = ''
480
-
481
- while content_length > 0
482
- chunk = client.readpartial([content_length, 4096].min)
483
- body += chunk
484
- content_length -= chunk.length
485
- end
486
-
487
- body
488
- end
489
-
490
- def build_http_response(status, headers, body)
491
- response = "HTTP/1.1 #{status}\r\n"
492
- headers.each { |key, value| response += "#{key}: #{value}\r\n" }
493
- response += "\r\n"
494
- response += body
495
- response
496
- end
497
-
498
- def handle_http_request(client, request_handler, http_headers)
499
- request_line = client.gets
500
- return unless request_line
501
-
502
- request = parse_http_request(request_line)
503
-
504
- headers = parse_http_headers(client)
505
-
506
- if request[:path] != '/'
507
- response = build_http_response('404 Not Found', http_headers, '')
508
- client.print(response)
509
- elsif request[:method] == 'OPTIONS'
510
- response = build_http_response('204 No Content', http_headers, '')
511
- client.print(response)
512
- elsif request[:method] == 'POST'
513
-
514
- content_length = headers['Content-Length'].to_i
515
- body = parse_post_body(client, content_length)
516
-
517
- begin
518
- json_data = JSON.parse(body)
519
- context = {
520
- 'headers' => headers
521
- }
522
-
523
- response_headers = http_headers.merge({
524
- 'Content-Type' => 'application/json'
525
- })
526
-
527
- result, error = request_handler.(json_data, context)
528
-
529
- if error
530
- response_json = error.to_json
531
- response = build_http_response('500 Internal Server Error', response_headers, response_json)
532
- client.print response
533
- elsif result
534
- response_json = result.to_json
535
- response = build_http_response('200 OK', response_headers, response_json)
536
- client.print response
537
- else
538
- response = build_http_response('500 Internal Server Error', response_headers, { 'message' => 'Request handler failed to return a result' }.to_json)
539
- client.print response
540
- end
541
-
542
- rescue JSON::ParserError
543
- response = build_http_response('400 Bad Request', http_headers, '')
544
- end
545
-
546
- else
547
- response = build_http_response('405 Method Not Allowed', http_headers, '')
548
- client.print(response)
549
- end
550
- client.close()
551
- end
552
-
553
- run = ->() do
554
-
555
- server = TCPServer.new('localhost', port)
556
- puts "Server listening on port #{port}"
557
-
558
- begin
559
- loop do
560
- client = server.accept
561
- Thread.new { handle_http_request(client, request_handler, http_headers) }
562
- end
563
- rescue Interrupt
564
- exit 1
565
- end
566
-
567
- end
568
-
569
- return run
570
- end
571
-
572
-
573
-
574
- def validate_server_options(options)
575
- if options.nil? || options.empty?
576
- return nil
577
- elsif !options.is_a?(Hash)
578
- return 'Options should be a hash'
579
- else
580
- if options.key?(:url)
581
- if !options[:url].is_a?(String)
582
- return '"url" option should be a string'
583
- elsif !options[:url].start_with?('/')
584
- return '"url" option should begin with a forward slash'
585
- end
586
- end
587
-
588
- if options.key?(:cors)
589
- if !options[:cors].is_a?(String) && !options[:cors].is_a?(TrueClass) && !options[:cors].is_a?(FalseClass)
590
- return '"cors" option should be a string or boolean'
591
- end
592
- end
593
- end
594
-
595
- return nil
596
- end
597
-
598
-
599
-
600
258
  def create_request_handler(routes)
601
259
  raise ArgumentError, 'A routes object is required' unless routes.is_a?(Hash)
602
260
 
@@ -884,8 +542,9 @@ def route_reducer(handler, request, context, timeout = nil)
884
542
  end
885
543
 
886
544
  # if request[:selector]
887
- # result = filter_object(result, request[:selector])
888
- # end
545
+ if request&.headers&._s
546
+ result = filter_object(result, request.headers._s)
547
+ end
889
548
 
890
549
  [request[:id], request[:route], result, nil]
891
550
  rescue => error
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: blest
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - JHunt
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-10-28 00:00:00.000000000 Z
11
+ date: 2024-12-29 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: The Ruby reference implementation of BLEST (Batch-able, Lightweight,
14
14
  Encrypted State Transfer), an improved communication protocol for web APIs which
@@ -36,7 +36,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - ">="
38
38
  - !ruby/object:Gem::Version
39
- version: '0'
39
+ version: '3.0'
40
40
  required_rubygems_version: !ruby/object:Gem::Requirement
41
41
  requirements:
42
42
  - - ">="