qeweney 0.11 → 0.15

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 256f14b402547865d5b7ce2357ef5ac79cd4f1f8e30129544450c7b8a60e39bb
4
- data.tar.gz: 94ecbd44f44c1e858873315780ae0661237692a20396bf0a06b96497ea93f76a
3
+ metadata.gz: a776c09a368fd3b348a685f43dcffb40320e6d181529427f7659e56815994735
4
+ data.tar.gz: 9ba936914a124c9a21923c147cf5d90b76cb6ea4dde46cbeeb558d99909a9d86
5
5
  SHA512:
6
- metadata.gz: 794a72410e59205ae66325aea81ee3d8a3b3e2bdaa7053118922e9a855f6d0e474d7d1e741787fe4b753a9c626e2672d9de4b95af14dc80c3f4a62175f72f0e5
7
- data.tar.gz: 1775acaaa03ab16287f18dd18deb8d4720febcd2d13d1e502afbe8525a244921bb2b2946dee15da949f1aae9ed803275c2faf7b4675e4f6f6542554769ca0b94
6
+ metadata.gz: 9f49f57a3c037646789565e1adb8c5ba129b1ed7e3f7b2ddbc3ec6a11c150d9e946728fcb2912af84c69070c1cefab00be707aed104b5eda2a7a9845cc14c5b8
7
+ data.tar.gz: 214702b726cb0c4c18fd380f3d4d985955002bad65a7e80ac0072da78d2014eea8a0c604256deefaab9c5adbb77fec0c155397df9dbcd868b2410f13a884e89a
@@ -0,0 +1 @@
1
+ github: ciconia
data/CHANGELOG.md CHANGED
@@ -1,3 +1,26 @@
1
+ ## 0.15 2022-01-08
2
+
3
+ - Add `TestAdapter` class for testing requests and responses
4
+ - Add `RoutingMethods#reject` method
5
+
6
+ ## 0.14 2021-08-10
7
+
8
+ - Fix Request#on with paths containing slashes
9
+
10
+ ## 0.13.1 2021-08-03
11
+
12
+ - Fix `Request#read`
13
+
14
+ ## 0.13 2021-08-02
15
+
16
+ - Restore `Request#complete?` method
17
+ - Add buffered_only argument to `Request#next_chunk`
18
+
19
+ ## 0.12 2021-07-30
20
+
21
+ - Remove `Request#consume` method
22
+ - Remove `Request#complete?` and associated methods
23
+
1
24
  ## 0.11 2021-07-26
2
25
 
3
26
  - Fix rack env (digital-fabric/tipi#11)
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- qeweney (0.11)
4
+ qeweney (0.15)
5
5
  escape_utils (~> 1.2.1)
6
6
 
7
7
  GEM
@@ -21,7 +21,7 @@ GEM
21
21
  ruby-progressbar (1.11.0)
22
22
 
23
23
  PLATFORMS
24
- ruby
24
+ x86_64-linux
25
25
 
26
26
  DEPENDENCIES
27
27
  benchmark-ips (~> 2.8.3)
@@ -31,4 +31,4 @@ DEPENDENCIES
31
31
  rake (~> 12.3.3)
32
32
 
33
33
  BUNDLED WITH
34
- 2.1.4
34
+ 2.3.3
data/README.md CHANGED
@@ -1,4 +1,8 @@
1
- # Qeweney
1
+ # Qeweney - add water, makes its own sauce!
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/qeweney.svg)](http://rubygems.org/gems/qeweney)
4
+ [![Modulation Test](https://github.com/digital-fabric/qeweney/workflows/Tests/badge.svg)](https://github.com/digital-fabric/qeweney/actions?query=workflow%3ATests)
5
+ [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/digital-fabric/qeweney/blob/master/LICENSE)
2
6
 
3
7
  ## Cross-library HTTP request / response API for servers
4
8
 
@@ -104,7 +104,7 @@ OptimizedRubyApp = ->(r) do
104
104
  else
105
105
  return r.respond('Hello')
106
106
  end
107
- elsif method == 'post'
107
+ elsif method == 'post'
108
108
  # puts 'Someone said Hello'
109
109
  return r.redirect('/')
110
110
  end
data/lib/qeweney/rack.rb CHANGED
@@ -67,12 +67,33 @@ module Qeweney
67
67
  end
68
68
  end
69
69
 
70
+ # TODO: integrate in env
71
+ # Implements a rack input stream:
72
+ # https://www.rubydoc.info/github/rack/rack/master/file/SPEC#label-The+Input+Stream
73
+ class InputStream
74
+ def initialize(request)
75
+ @request = request
76
+ end
77
+
78
+ def gets; end
79
+
80
+ def read(length = nil, outbuf = nil); end
81
+
82
+ def each(&block)
83
+ @request.each_chunk(&block)
84
+ end
85
+
86
+ def rewind; end
87
+ end
88
+
70
89
  def self.rack_env_from_request(request)
71
90
  Hash.new do |h, k|
72
91
  h[k] = rack_env_value_from_request(request, k)
73
92
  end
74
93
  end
75
94
 
95
+ # TODO: improve conformance to rack spec
96
+ # TODO: set values like port, scheme etc from actual connection
76
97
  RACK_ENV = {
77
98
  'SCRIPT_NAME' => '',
78
99
  'rack.version' => [1, 3],
@@ -89,7 +110,7 @@ module Qeweney
89
110
  'rack.multipart.buffer_size' => nil,
90
111
  'rack.multipar.tempfile_factory' => nil
91
112
  }
92
-
113
+
93
114
  HTTP_HEADER_RE = /^HTTP_(.+)$/.freeze
94
115
 
95
116
  def self.rack_env_value_from_request(request, key)
@@ -15,28 +15,29 @@ module Qeweney
15
15
  extend RequestInfoClassMethods
16
16
 
17
17
  attr_reader :headers, :adapter
18
- attr_accessor :__next__
19
-
18
+
20
19
  def initialize(headers, adapter)
21
20
  @headers = headers
22
21
  @adapter = adapter
23
22
  end
24
-
23
+
25
24
  def buffer_body_chunk(chunk)
26
25
  @buffered_body_chunks ||= []
27
26
  @buffered_body_chunks << chunk
28
27
  end
29
28
 
30
- def next_chunk
29
+ def next_chunk(buffered_only = false)
31
30
  if @buffered_body_chunks
32
31
  chunk = @buffered_body_chunks.shift
33
32
  @buffered_body_chunks = nil if @buffered_body_chunks.empty?
34
33
  return chunk
34
+ elsif buffered_only
35
+ return nil
35
36
  end
36
37
 
37
- @message_complete ? nil : @adapter.get_body_chunk(self)
38
+ @adapter.get_body_chunk(self, buffered_only)
38
39
  end
39
-
40
+
40
41
  def each_chunk
41
42
  if @buffered_body_chunks
42
43
  while (chunk = @buffered_body_chunks.shift)
@@ -44,60 +45,51 @@ module Qeweney
44
45
  end
45
46
  @buffered_body_chunks = nil
46
47
  end
47
- while !@message_complete && (chunk = @adapter.get_body_chunk(self))
48
+ while (chunk = @adapter.get_body_chunk(self))
48
49
  yield chunk
49
50
  end
50
51
  end
51
52
 
52
- def complete!(keep_alive = nil)
53
- @message_complete = true
54
- @keep_alive = keep_alive
55
- end
56
-
57
- def complete?
58
- @message_complete
59
- end
60
-
61
- def consume
62
- @adapter.consume_request(self)
63
- end
64
-
65
- def keep_alive?
66
- @keep_alive
67
- end
68
-
69
53
  def read
70
- buf = @buffered_body_chunks ? @buffered_body_chunks.join : nil
71
- while (chunk = @adapter.get_body_chunk(self))
72
- (buf ||= +'') << chunk
54
+ if @buffered_body_chunks
55
+ body = @buffered_body_chunks.join
56
+ if !complete?
57
+ rest = @adapter.get_body(self)
58
+ body << rest if rest
59
+ end
60
+ @buffered_body_chunks = nil
61
+ return body
73
62
  end
74
- @buffered_body_chunks = nil
75
- buf
63
+ @adapter.get_body(self)
76
64
  end
77
65
  alias_method :body, :read
78
-
66
+
67
+ def complete?
68
+ @adapter.complete?(self)
69
+ end
70
+
79
71
  def respond(body, headers = {})
80
72
  @adapter.respond(self, body, headers)
81
73
  @headers_sent = true
82
74
  end
83
-
75
+
84
76
  def send_headers(headers = {}, empty_response = false)
85
77
  return if @headers_sent
86
-
78
+
87
79
  @headers_sent = true
88
80
  @adapter.send_headers(self, headers, empty_response: empty_response)
89
81
  end
90
-
82
+
91
83
  def send_chunk(body, done: false)
92
84
  send_headers({}) unless @headers_sent
93
-
85
+
94
86
  @adapter.send_chunk(self, body, done: done)
95
87
  end
96
88
  alias_method :<<, :send_chunk
97
-
89
+
98
90
  def finish
99
91
  send_headers({}) unless @headers_sent
100
-
92
+
101
93
  @adapter.finish(self)
102
94
  end
103
95
 
@@ -25,39 +25,39 @@ module Qeweney
25
25
  def protocol
26
26
  @protocol ||= @adapter.protocol
27
27
  end
28
-
28
+
29
29
  def method
30
30
  @method ||= @headers[':method'].downcase
31
31
  end
32
-
32
+
33
33
  def scheme
34
34
  @scheme ||= @headers[':scheme']
35
35
  end
36
-
36
+
37
37
  def uri
38
38
  @uri ||= URI.parse(@headers[':path'] || '')
39
39
  end
40
-
40
+
41
41
  def full_uri
42
42
  @full_uri = "#{scheme}://#{host}#{uri}"
43
43
  end
44
-
44
+
45
45
  def path
46
46
  @path ||= uri.path
47
47
  end
48
-
48
+
49
49
  def query_string
50
50
  @query_string ||= uri.query
51
51
  end
52
-
52
+
53
53
  def query
54
54
  return @query if @query
55
-
55
+
56
56
  @query = (q = uri.query) ? parse_query(q) : {}
57
57
  end
58
-
58
+
59
59
  QUERY_KV_REGEXP = /([^=]+)(?:=(.*))?/
60
-
60
+
61
61
  def parse_query(query)
62
62
  query.split('&').each_with_object({}) do |kv, h|
63
63
  k, v = kv.match(QUERY_KV_REGEXP)[1..2]
@@ -89,13 +89,13 @@ module Qeweney
89
89
 
90
90
  COOKIE_RE = /^([^=]+)=(.*)$/.freeze
91
91
  SEMICOLON = ';'
92
-
92
+
93
93
  def parse_cookies(cookies)
94
94
  return {} unless cookies
95
95
 
96
96
  cookies.split(SEMICOLON).each_with_object({}) do |c, h|
97
97
  raise BadRequestError, 'Invalid cookie format' unless c.strip =~ COOKIE_RE
98
-
98
+
99
99
  key, value = Regexp.last_match[1..2]
100
100
  h[key] = EscapeUtils.unescape_uri(value)
101
101
  end
@@ -151,7 +151,7 @@ module Qeweney
151
151
  break if header.empty?
152
152
 
153
153
  next unless header =~ /^([^\:]+)\:\s?(.+)$/
154
-
154
+
155
155
  headers[Regexp.last_match(1).downcase] = Regexp.last_match(2)
156
156
  end
157
157
  # remove trailing \r\n
@@ -164,16 +164,18 @@ module Qeweney
164
164
  MAX_PARAMETER_VALUE_SIZE = 2**20 # 1MB
165
165
 
166
166
  def parse_urlencoded_form_data(body)
167
+ return {} unless body
168
+
167
169
  body.force_encoding(Encoding::UTF_8) unless body.encoding == Encoding::UTF_8
168
170
  body.split('&').each_with_object({}) do |i, m|
169
171
  raise 'Invalid parameter format' unless i =~ PARAMETER_RE
170
-
172
+
171
173
  k = Regexp.last_match(1)
172
174
  raise 'Invalid parameter size' if k.size > MAX_PARAMETER_NAME_SIZE
173
-
175
+
174
176
  v = Regexp.last_match(2)
175
177
  raise 'Invalid parameter size' if v.size > MAX_PARAMETER_VALUE_SIZE
176
-
178
+
177
179
  m[EscapeUtils.unescape_uri(k)] = EscapeUtils.unescape_uri(v)
178
180
  end
179
181
  end
@@ -14,7 +14,7 @@ module Qeweney
14
14
  def file_stat_to_etag(stat)
15
15
  "#{stat.mtime.to_i.to_s(36)}#{stat.size.to_s(36)}"
16
16
  end
17
-
17
+
18
18
  def file_stat_to_last_modified(stat)
19
19
  stat.mtime.httpdate
20
20
  end
@@ -114,7 +114,7 @@ module Qeweney
114
114
  if (modified_since = headers['if-modified-since'])
115
115
  return true if modified_since == last_modified
116
116
  end
117
-
117
+
118
118
  false
119
119
  end
120
120
 
@@ -10,7 +10,7 @@ module Qeweney
10
10
  (@path_parts ||= path.split('/'))[@path_parts_idx ||= 1]
11
11
  res = catch(:stop) { yield self }
12
12
  return if res == :found
13
-
13
+
14
14
  respond(nil, ':status' => 404)
15
15
  end
16
16
 
@@ -29,22 +29,24 @@ module Qeweney
29
29
  @path_parts.empty? ? '/' : "/#{@path_parts[@path_parts_idx..-1].join('/')}"
30
30
  end
31
31
 
32
- def enter_route
33
- @path_parts_idx += 1
32
+ def enter_route(depth = 1)
33
+ @path_parts_idx += depth
34
34
  end
35
35
 
36
- def leave_route
37
- @path_parts_idx -= 1
36
+ def leave_route(depth = 1)
37
+ @path_parts_idx -= depth
38
38
  end
39
39
 
40
- def on(route = nil, &block)
41
- if route
42
- return unless @path_parts[@path_parts_idx] == route
43
- end
44
-
45
- enter_route
40
+ def on(route, &block)
41
+ return route_found(&block) unless route
42
+
43
+ route_parts = route.split('/')
44
+ route_length = route_parts.size
45
+ return unless @path_parts[@path_parts_idx, route_length] == route_parts
46
+
47
+ enter_route(route_length)
46
48
  route_found(&block)
47
- leave_route
49
+ leave_route(route_length)
48
50
  end
49
51
 
50
52
  def is(route = '/', &block)
@@ -70,22 +72,22 @@ module Qeweney
70
72
 
71
73
  route_found(&block)
72
74
  end
73
-
75
+
74
76
  def on_get(route = nil, &block)
75
77
  return unless method == 'get'
76
-
78
+
77
79
  on(route, &block)
78
80
  end
79
-
81
+
80
82
  def on_post(route = nil, &block)
81
83
  return unless method == 'post'
82
-
84
+
83
85
  on(route, &block)
84
86
  end
85
87
 
86
88
  def on_options(route = nil, &block)
87
89
  return unless method == 'options'
88
-
90
+
89
91
  on(route, &block)
90
92
  end
91
93
 
@@ -122,6 +124,11 @@ module Qeweney
122
124
  on_upgrade('websocket', &block)
123
125
  end
124
126
 
127
+ def reject(body, status)
128
+ respond(body, ':status' => status)
129
+ throw :stop, :found
130
+ end
131
+
125
132
  def stop_routing
126
133
  throw :stop, :found
127
134
  end
@@ -5,7 +5,7 @@ module Qeweney
5
5
  module Status
6
6
  # translated from https://golang.org/pkg/net/http/#pkg-constants
7
7
  # https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
8
-
8
+
9
9
  CONTINUE = 100 # RFC 7231, 6.2.1
10
10
  SWITCHING_PROTOCOLS = 101 # RFC 7231, 6.2.2
11
11
  PROCESSING = 102 # RFC 2518, 10.1
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Qeweney
4
+ class TestAdapter
5
+ attr_reader :body, :headers
6
+
7
+ def get_body_chunk
8
+ nil
9
+ end
10
+
11
+ def respond(req, body, headers)
12
+ @body = body
13
+ @headers = headers
14
+ end
15
+
16
+ def status
17
+ headers[':status']
18
+ end
19
+
20
+ def self.mock(headers = {})
21
+ headers[':method'] ||= ''
22
+ headers[':path'] ||= ''
23
+ Request.new(headers, new)
24
+ end
25
+ end
26
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Qeweney
4
- VERSION = '0.11'
4
+ VERSION = '0.15'
5
5
  end
data/lib/qeweney.rb CHANGED
@@ -5,4 +5,3 @@ end
5
5
 
6
6
  require_relative 'qeweney/request.rb'
7
7
  require_relative 'qeweney/status.rb'
8
-
data/test/helper.rb CHANGED
@@ -8,7 +8,6 @@ require 'fileutils'
8
8
  require_relative './coverage' if ENV['COVERAGE']
9
9
 
10
10
  require 'minitest/autorun'
11
- require 'minitest/reporters'
12
11
 
13
12
  module Qeweney
14
13
  class MockAdapter
@@ -35,7 +34,3 @@ module Qeweney
35
34
  end
36
35
  end
37
36
  end
38
-
39
- Minitest::Reporters.use! [
40
- Minitest::Reporters::SpecReporter.new
41
- ]
@@ -73,7 +73,7 @@ class StaticFileResponeTest < MiniTest::Test
73
73
 
74
74
  deflate = Zlib::Deflate.new
75
75
  deflated_content = deflate.deflate(@content, Zlib::FINISH)
76
-
76
+
77
77
  assert_equal [
78
78
  [:respond, r, deflated_content, {
79
79
  'etag' => @etag,
@@ -94,7 +94,7 @@ class StaticFileResponeTest < MiniTest::Test
94
94
  z.flush
95
95
  z.close
96
96
  gzipped_content = buf.string
97
-
97
+
98
98
  assert_equal [
99
99
  [:respond, r, gzipped_content, {
100
100
  'etag' => @etag,
@@ -127,7 +127,7 @@ class UpgradeTest < MiniTest::Test
127
127
  }]
128
128
  ], r.response_calls
129
129
 
130
-
130
+
131
131
  r = Qeweney.mock
132
132
  r.upgrade('df', { 'foo' => 'bar' })
133
133
 
@@ -148,7 +148,7 @@ class UpgradeTest < MiniTest::Test
148
148
  'sec-websocket-version' => '23',
149
149
  'sec-websocket-key' => 'abcdefghij'
150
150
  )
151
-
151
+
152
152
  assert_equal 'websocket', r.upgrade_protocol
153
153
 
154
154
  r.upgrade_to_websocket('foo' => 'baz')
@@ -179,7 +179,7 @@ class ServeRackTest < MiniTest::Test
179
179
  })
180
180
 
181
181
  assert_equal [
182
- [:respond, r, 'get /foo/bar', {':status' => 404, 'Foo' => 'Bar' }]
182
+ [:respond, r, 'GET /foo/bar', {':status' => 404, 'Foo' => 'Bar' }]
183
183
  ], r.response_calls
184
184
  end
185
185
  end
data/test/test_routing.rb CHANGED
@@ -81,7 +81,7 @@ class RoutingTest < MiniTest::Test
81
81
  default_relative_path = r.route_relative_path
82
82
  r.on_root { r.respond(File.join('ROOT', r.route_relative_path)) }
83
83
  r.on('foo') { r.respond(File.join('FOO', r.route_relative_path)) }
84
- r.on('bar') {
84
+ r.on('bar') {
85
85
  r.on('baz') { r.respond(File.join('BAR/BAZ', r.route_relative_path)) }
86
86
  r.default { r.respond(File.join('BAR', r.route_relative_path)) }
87
87
  }
@@ -114,4 +114,16 @@ class RoutingTest < MiniTest::Test
114
114
  assert_equal '/baz/d/e/f', default_relative_path
115
115
  assert_equal [[:respond, r, '/d/e/f', {}]], r.response_calls
116
116
  end
117
+
118
+ def test_well_known
119
+ app = Qeweney.route do |r|
120
+ r.on('.well-known/acme-challenge') { r.respond(r.route_relative_path) }
121
+ r.default { r.respond('not found') }
122
+ end
123
+
124
+ r = Qeweney.mock(':path' => '/.well-known/acme-challenge/foo')
125
+ app.(r)
126
+ assert_equal [[:respond, r, '/foo', {}]], r.response_calls
127
+ end
117
128
  end
129
+
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'helper'
4
+ require 'qeweney/test_adapter'
5
+
6
+ class TestAdapterTest < MiniTest::Test
7
+ def test_test_adapter
8
+ adapter = Qeweney::TestAdapter.new
9
+ req = Qeweney::Request.new({ ':path' => '/foo' }, adapter)
10
+ req.respond('bar', 'Content-Type' => 'baz')
11
+
12
+ assert_equal 'bar', adapter.body
13
+ assert_equal({'Content-Type' => 'baz'}, adapter.headers)
14
+ end
15
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: qeweney
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.11'
4
+ version: '0.15'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-07-26 00:00:00.000000000 Z
11
+ date: 2022-01-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: escape_utils
@@ -80,13 +80,14 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: 2.8.3
83
- description:
83
+ description:
84
84
  email: sharon@noteflakes.com
85
85
  executables: []
86
86
  extensions: []
87
87
  extra_rdoc_files:
88
88
  - README.md
89
89
  files:
90
+ - ".github/FUNDING.yml"
90
91
  - ".github/workflows/test.yml"
91
92
  - ".gitignore"
92
93
  - CHANGELOG.md
@@ -105,6 +106,7 @@ files:
105
106
  - lib/qeweney/response.rb
106
107
  - lib/qeweney/routing.rb
107
108
  - lib/qeweney/status.rb
109
+ - lib/qeweney/test_adapter.rb
108
110
  - lib/qeweney/version.rb
109
111
  - qeweney.gemspec
110
112
  - test/helper.rb
@@ -112,12 +114,13 @@ files:
112
114
  - test/test_request_info.rb
113
115
  - test/test_response.rb
114
116
  - test/test_routing.rb
117
+ - test/test_test_adapter.rb
115
118
  homepage: http://github.com/digital-fabric/qeweney
116
119
  licenses:
117
120
  - MIT
118
121
  metadata:
119
122
  source_code_uri: https://github.com/digital-fabric/qeweney
120
- post_install_message:
123
+ post_install_message:
121
124
  rdoc_options:
122
125
  - "--title"
123
126
  - Qeweney
@@ -136,8 +139,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
136
139
  - !ruby/object:Gem::Version
137
140
  version: '0'
138
141
  requirements: []
139
- rubygems_version: 3.1.6
140
- signing_key:
142
+ rubygems_version: 3.3.3
143
+ signing_key:
141
144
  specification_version: 4
142
145
  summary: Qeweney - cross library HTTP request / response API
143
146
  test_files: []