qeweney 0.4 → 0.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bfb8f3a7995ccb0d35e79ecb1cbc9f2556ee4bc5e0bc7d61d3f1278395ea47df
4
- data.tar.gz: 04cf0bbb7ebe8a701ab7a7b4e912e461ffc744ece02a1b75ff791eba22036da6
3
+ metadata.gz: 82e2e4adbe7c5c163bb7252c666659baea7758f726a3426f9af837c0d09408ac
4
+ data.tar.gz: b7aee9e0c09876fd2730905dc61617945636bee6c3891bae45573f910aa67398
5
5
  SHA512:
6
- metadata.gz: 4f2d77164b052af1cdae718a2c44ccb3c0313bcc73cc87b637b9a1ccabee6701fecd5b223020b4ca854e74d218fefc0101a6150a4e426a9b5c2f1e41a8d6a951
7
- data.tar.gz: 0030e7024d6a42f0301ba515e49f1e8da52e3b65368b515d81b520396a187dc0adf8107b32b8f0d08eb0f62d0e3c6f2f9e34f617a69444211b01b4971d950ed2
6
+ metadata.gz: 1225cb063c858b5e3f0e0e465d778771c8a8a4fec55d5b431d93489d637a8768c1787c1ea28aa6417f83d7666504e433bf338e48b483616d3b9165bb8e1f3465
7
+ data.tar.gz: 4fececba98bf9f24e0accef005c36eb62fe13bb5955e380b9324de71660c6dd156c1fb077a13051d1855b971a685be712f0e8873752cb414466974a15d8b8b4f
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## 0.5 2021-02-15
2
+
3
+ - Implement upgrade and WebSocket upgrade responses
4
+
1
5
  ## 0.4 2021-02-12
2
6
 
3
7
  - Implement caching and compression for serving static files
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- qeweney (0.4)
4
+ qeweney (0.5)
5
5
  escape_utils (~> 1.2.1)
6
6
 
7
7
  GEM
data/TODO.md CHANGED
@@ -3,97 +3,79 @@
3
3
  - `#serve_file(path, opts)`
4
4
  - See here: https://golang.org/pkg/net/http/#ServeFile
5
5
  - support for `Range` header
6
- - support for caching (specified in opts)
7
- - support for serving from routing path:
8
-
9
- ```ruby
10
- req.route do
11
- req.on 'assets' do
12
- req.serve_file(req.routing_path, base_path: STATIC_PATH)
13
- end
14
- end
15
-
16
- # or convenience method
17
- req.route do
18
- req.on_static_route 'assets', base_path: STATIC_PATH
19
- end
20
- ```
21
6
 
22
7
  - `#serve_content(io, opts)`
23
8
  - See here: https://golang.org/pkg/net/http/#ServeContent
24
9
  - support for `Range` header
25
- - support for caching (specified in opts)
26
- - usage:
27
10
 
28
- ```ruby
29
- req.route do
30
- req.on 'mypdf' do
31
- File.open('my.pdf', 'r') { |f| req.serve_content(io) }
32
- end
33
- end
34
- ```
11
+ ## route on host
35
12
 
36
- ## Caching
13
+ - `#on_host`:
37
14
 
38
- ```ruby
39
- req.route do
40
- req.on 'assets' do
41
- # setting cache to true implies the following:
42
- # - etag (calculated from file stat)
43
- # - last-modified (from file stat)
44
- # - vary: Accept-Encoding
45
-
46
- # before responding, looks at the following headers
47
- # if-modified-since: (date from client's cache)
48
- # if-none-match: (etag from client's cache)
49
- # cache-control: no-cache will prevent caching
50
- req.serve_file(path, base_path: STATIC_PATH, cache: true)
51
-
52
- # We can control this manually instead:
53
- req.serve_file(path, base_path: STATIC_PATH, cache: {
54
- etag: 'blahblah',
55
- last_modified: Time.now - 365*86400,
56
- vary: 'Accept-Encoding, User-Agent'
57
- })
15
+ ```ruby
16
+ req.route do
17
+ req.on_host 'example.com' do
18
+ req.redirect "https://www.example.com#{req.uri}"
19
+ end
58
20
  end
59
- end
60
- ```
21
+ ```
61
22
 
62
- So, the algorithm:
23
+ - `#on_http`:
63
24
 
64
- ```ruby
65
- def validate_client_cache(path)
66
- return false if headers['cache-control'] == 'no-cache'
67
-
68
- stat = File.stat(path)
69
- etag = file_stat_to_etag(path, stat)
70
- return false if headers['if-none-match'] != etag
25
+ ```ruby
26
+ req.route do
27
+ req.on_http do
28
+ req.redirect "https://#{req.host}#{req.uri}"
29
+ end
30
+ end
31
+ ```
71
32
 
72
- modified = file_stat_to_modified_stamp(stat)
73
- return false if headers['if-modified-since'] != modified
33
+ - shorthand:
74
34
 
75
- true
76
- end
35
+ ```ruby
36
+ req.route do
37
+ req.on_http { req.redirect_to_https }
38
+ req.on_host 'example.com' do
39
+ req.redirect_to_host('www.example.com')
40
+ end
41
+ end
42
+ ```
77
43
 
78
- def file_stat_to_etag(path, stat)
79
- "#{stat.mtime.to_i.to_s(36)}#{stat.size.to_s(36)}"
80
- end
44
+ ## templates
81
45
 
82
- require 'time'
46
+ - needs to be pluggable - allow any kind of template
83
47
 
84
- def file_stat_to_modified_stamp(stat)
85
- stat.mtime.httpdate
48
+ ```ruby
49
+ WEBSITE_PATH = File.join(__dir__, 'docs')
50
+ STATIC_PATH = File.join(WEBSITE_PATH, 'static')
51
+ LAYOUTS_PATH = File.join(WEBSITE_PATH, '_layouts')
52
+
53
+ PAGES = Tipi::PageManager(
54
+ base_path: WEBSITE_PATH,
55
+ engine: :markdown,
56
+ layouts: LAYOUTS_PATH
57
+ )
58
+
59
+ app = Tipi.app do |r|
60
+ r.on 'static' do
61
+ r.serve_file(r.routing_path, base_path: ASSETS_PATH)
62
+ end
63
+
64
+ r.default do
65
+ PAGES.serve(r)
66
+ end
86
67
  end
87
68
  ```
88
69
 
89
- ## response compression
70
+ ## Rewriting URLs
90
71
 
91
72
  ```ruby
92
- req.route do
93
- req.on 'assets' do
94
- # gzip on the fly
95
- req.serve_file(path, base_path: STATIC_PATH, gzip: :gzip)
96
- end
97
- end
73
+ app = Tipi.app do |r|
74
+ r.rewrite '/' => '/docs/index.html'
75
+ r.rewrite '/docs' => '/docs/'
76
+ r.rewrite '/docs/' => '/docs/index.html'
98
77
 
78
+ # or maybe
79
+ r.on '/docs/'
80
+ end
99
81
  ```
@@ -17,6 +17,10 @@ module Qeweney
17
17
  connection == 'upgrade' && @headers['upgrade']&.downcase
18
18
  end
19
19
 
20
+ def websocket_version
21
+ headers['sec-websocket-version'].to_i
22
+ end
23
+
20
24
  def protocol
21
25
  @protocol ||= @adapter.protocol
22
26
  end
@@ -3,6 +3,7 @@
3
3
  require 'time'
4
4
  require 'zlib'
5
5
  require 'stringio'
6
+ require 'digest/sha1'
6
7
 
7
8
  require_relative 'status'
8
9
 
@@ -20,10 +21,44 @@ module Qeweney
20
21
  end
21
22
 
22
23
  module ResponseMethods
24
+ def upgrade(protocol, custom_headers = nil)
25
+ upgrade_headers = {
26
+ ':status' => Status::SWITCHING_PROTOCOLS,
27
+ 'Upgrade' => protocol,
28
+ 'Connection' => 'upgrade'
29
+ }
30
+ upgrade_headers.merge!(custom_headers) if custom_headers
31
+
32
+ respond(nil, upgrade_headers)
33
+ end
34
+
35
+ WEBSOCKET_GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
36
+
37
+ def upgrade_to_websocket(custom_headers = nil)
38
+ key = "#{headers['sec-websocket-key']}#{WEBSOCKET_GUID}"
39
+ upgrade_headers = {
40
+ 'Sec-WebSocket-Accept' => Digest::SHA1.base64digest(key)
41
+ }
42
+ upgrade_headers.merge!(custom_headers) if custom_headers
43
+ upgrade('websocket', upgrade_headers)
44
+
45
+ adapter.websocket_connection(self)
46
+ end
47
+
23
48
  def redirect(url, status = Status::FOUND)
24
49
  respond(nil, ':status' => status, 'Location' => url)
25
50
  end
26
51
 
52
+ def redirect_to_https(status = Status::MOVED_PERMANENTLY)
53
+ secure_uri = "https://#{host}#{uri}"
54
+ redirect(secure_uri, status)
55
+ end
56
+
57
+ def redirect_to_host(new_host, status = Status::FOUND)
58
+ secure_uri = "//#{new_host}#{uri}"
59
+ redirect(secure_uri, status)
60
+ end
61
+
27
62
  def serve_file(path, opts)
28
63
  full_path = file_full_path(path, opts)
29
64
  stat = File.stat(full_path)
@@ -44,6 +44,18 @@ module Qeweney
44
44
 
45
45
  route_found(&block)
46
46
  end
47
+
48
+ def on_host(route, &block)
49
+ return unless host == route
50
+
51
+ route_found(&block)
52
+ end
53
+
54
+ def on_plain_http(route, &block)
55
+ return unless scheme == 'http'
56
+
57
+ route_found(&block)
58
+ end
47
59
 
48
60
  def on_get(route = nil, &block)
49
61
  return unless method == 'get'
@@ -86,8 +98,22 @@ module Qeweney
86
98
  route_found(&block)
87
99
  end
88
100
 
101
+ def on_upgrade(protocol, &block)
102
+ return unless upgrade_protocol == protocol
103
+
104
+ route_found(&block)
105
+ end
106
+
107
+ def on_websocket_upgrade(&block)
108
+ on_upgrade('websocket', &block)
109
+ end
110
+
89
111
  def stop_routing
90
- yield if block_given?
112
+ throw :stop, :found
113
+ end
114
+
115
+ def default
116
+ yield
91
117
  throw :stop, :found
92
118
  end
93
119
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Qeweney
4
- VERSION = '0.4'
4
+ VERSION = '0.5'
5
5
  end
@@ -113,3 +113,56 @@ class StaticFileResponeTest < MiniTest::Test
113
113
  ], r.response_calls
114
114
  end
115
115
  end
116
+
117
+ class UpgradeTest < MiniTest::Test
118
+ def test_upgrade
119
+ r = Qeweney.mock
120
+ r.upgrade('df')
121
+
122
+ assert_equal [
123
+ [:respond, nil, {
124
+ ':status' => 101,
125
+ 'Upgrade' => 'df',
126
+ 'Connection' => 'upgrade'
127
+ }]
128
+ ], r.response_calls
129
+
130
+
131
+ r = Qeweney.mock
132
+ r.upgrade('df', { 'foo' => 'bar' })
133
+
134
+ assert_equal [
135
+ [:respond, nil, {
136
+ ':status' => 101,
137
+ 'Upgrade' => 'df',
138
+ 'Connection' => 'upgrade',
139
+ 'foo' => 'bar'
140
+ }]
141
+ ], r.response_calls
142
+ end
143
+
144
+ def test_websocket_upgrade
145
+ r = Qeweney.mock(
146
+ 'connection' => 'upgrade',
147
+ 'upgrade' => 'websocket',
148
+ 'sec-websocket-version' => '23',
149
+ 'sec-websocket-key' => 'abcdefghij'
150
+ )
151
+
152
+ assert_equal 'websocket', r.upgrade_protocol
153
+
154
+ r.upgrade_to_websocket('foo' => 'baz')
155
+ accept = Digest::SHA1.base64digest('abcdefghij258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
156
+
157
+ assert_equal [
158
+ [:respond, nil, {
159
+ ':status' => 101,
160
+ 'Upgrade' => 'websocket',
161
+ 'Connection' => 'upgrade',
162
+ 'foo' => 'baz',
163
+ 'Sec-WebSocket-Accept' => accept
164
+ }],
165
+ [:websocket_connection, r]
166
+ ], r.response_calls
167
+ end
168
+ 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.4'
4
+ version: '0.5'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-02-12 00:00:00.000000000 Z
11
+ date: 2021-02-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: escape_utils