blest 1.0.0 → 1.0.2

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