qeweney 0.4 → 0.5

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: 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