blest 1.0.0 → 1.0.1

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 +1 -31
  4. data/lib/blest.rb +0 -342
  5. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c4f44d941bb783457e67fa52ada69792f5f645a76bac399165d59c73f4ba7b4b
4
- data.tar.gz: 8f7ee45d396ae4cab3c8178f1bef7e6605612efb94e259cbb35bcd3da842e347
3
+ metadata.gz: dda8d494a672ecd29209b9971076decfaec476253157103dc75a6d74cfc2f327
4
+ data.tar.gz: dee130556eb32a00cdbb14ae3f5c160133241763061d6558592d1712963cbfc4
5
5
  SHA512:
6
- metadata.gz: aecfa3005a9a29bcfd4b4f501710a0f8a49251ff7fc62f4043bfe8e5b0e7f6c8e2ddbb09df2ae14c08bc7617a09ab08d84ed52d9023272bd9bb05dabd1fb8e41
7
- data.tar.gz: 2855b895975b23f418e0e084ecd3d15bd8df43764a896aca1f8fa5788d21e74ab7fb5ff5fdc4bbcd79a074dff7ce2f14bb3a10ee4e497d8ef1d63b1a645066c3
6
+ metadata.gz: 4407b468d713463014fa4449efb4e2be40ac59d93fbca2302d1ddc13aa036907b0ba6b4067cd92e1e90a64e37dcca3cd29b0aec605722fd7a5edf4458058245f
7
+ data.tar.gz: f1c050cdaa44297e90c968ba99be88f3fb440743a959e9ab32286d04ce42a4dc886059bb598c1c29ab0d38d41d7bccc384a86c9d114d5f6398f68da275afb8a3
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,36 +24,6 @@ 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
29
  The following example uses Sinatra.
@@ -111,7 +81,7 @@ client = HttpClient.new('http://localhost:8080', max_batch_size = 25, buffer_del
111
81
 
112
82
  # Send a request
113
83
  begin
114
- result = client.request('greet', { 'name': 'Steve' }, ['greeting']).value
84
+ result = client.request('greet', { 'name': 'Steve' }, { 'auth': 'myToken' }).value
115
85
  # Do something with the result
116
86
  rescue => error
117
87
  # 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
 
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.1
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-10-31 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