ruby_wasm_ic_rack 0.1.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5723c6f1d158956c8dede6d1ddc98b5b5f048ec51931df4d1eadd8531d59aea4
4
+ data.tar.gz: '0119fd70d66b909e5185bd0b17f6570b60550ff99de4d8338a496382882825bc'
5
+ SHA512:
6
+ metadata.gz: d02430595e7709b5d26efedab2b78f0693e9c6788479942eafd53ef2d2ee1215f86cd50893f9e0a0582dbbe5dbc5f9e4f945d32113da497f604862fd212a94f7
7
+ data.tar.gz: 4528b16357279759bb9195f15dc668923e8826c2edadc68b9cbe198eda50ddea7c49b437f9e13243ed3e5cd76ef219678ff7d1d3cd149447c555a59e92f48911
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 the ruby_wasm_builder authors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,115 @@
1
+ # ruby_wasm_ic_rack
2
+
3
+ Serve a [Rack](https://github.com/rack/rack) app from a Ruby canister on the
4
+ Internet Computer, through the IC HTTP gateway. For canisters packed by
5
+ [ruby_wasm_builder](https://gitlab.com/phrug/ruby_wasm_builder) from 0.7.0.
6
+
7
+ Pure Ruby, no dependencies. It works the same on the `minimal` and `full`
8
+ Builds: it names no standard library, no C extension and no `Encoding`
9
+ constant.
10
+
11
+ ## Carrying it
12
+
13
+ A canister has no `bundle install`. Carry the gem's files in your Application,
14
+ as you carry Rack:
15
+
16
+ ```bash
17
+ gem fetch ruby_wasm_ic_rack && gem unpack ruby_wasm_ic_rack-*.gem
18
+ cp -r ruby_wasm_ic_rack-*/lib my_app/vendor/ruby_wasm_ic_rack
19
+ ```
20
+
21
+ ```ruby
22
+ # app.rb
23
+ $LOAD_PATH.unshift(File.join(__dir__, "vendor/ruby_wasm_ic_rack"))
24
+ require "ruby_wasm_ic_rack"
25
+ ```
26
+
27
+ To move to a newer version, unpack it and copy its `lib/` again.
28
+
29
+ ## Declaring and serving the gateway's methods
30
+
31
+ From ruby_wasm_builder 0.8.0, one word in `ic-interface.rb` declares the
32
+ gateway's types and both methods:
33
+
34
+ ```ruby
35
+ # ic-interface.rb
36
+ http_gateway
37
+ ```
38
+
39
+ On 0.7.0, write them out (this is what the word declares):
40
+
41
+ ```ruby
42
+ type :HeaderField, Tuple[Text, Text]
43
+ type :HttpRequest, record(method: Text, url: Text, headers: Vec[:HeaderField], body: Blob,
44
+ certificate_version: Opt[Nat16])
45
+ type :HttpUpdateRequest, record(method: Text, url: Text, headers: Vec[:HeaderField], body: Blob)
46
+ type :HttpResponse, record(status_code: Nat16, headers: Vec[:HeaderField], body: Blob, upgrade: Opt[Bool])
47
+
48
+ query :http_request, takes: [:HttpRequest], returns: :HttpResponse
49
+ update :http_request_update, takes: [:HttpUpdateRequest], returns: :HttpResponse
50
+ ```
51
+
52
+ Then hand both to your app:
53
+
54
+ ```ruby
55
+ # app.rb
56
+ APP = ->(env) { [200, { "content-type" => "text/plain" }, ["hello from #{env['PATH_INFO']}"]] }
57
+
58
+ def http_request(req) = RubyWasmIcRack.query(APP, req)
59
+ def http_request_update(req) = RubyWasmIcRack.update(APP, req)
60
+ ```
61
+
62
+ ## What it does
63
+
64
+ - **The env:** `REQUEST_METHOD`, `PATH_INFO`, `QUERY_STRING`, `SERVER_NAME`
65
+ (from `host`), `SERVER_PORT` 443, `rack.url_scheme` `https`, every header as
66
+ `HTTP_*` (`CONTENT_TYPE` and `CONTENT_LENGTH` as Rack names them), a repeated
67
+ header joined with `, `, and `rack.input` over the body.
68
+ - **The response:** a header whose value is an Array, or lines joined by
69
+ `"\n"` (Rack 3), goes out as one header each, so two `set-cookie` headers
70
+ arrive as two. The body is joined and closed.
71
+ - **Upgrades.** The gateway calls `http_request` as a query, and a query keeps
72
+ nothing it writes. So `RubyWasmIcRack.query` answers only `GET`, `HEAD` and `OPTIONS`
73
+ and asks the gateway to upgrade everything else, which then calls
74
+ `http_request_update` through consensus. A `GET` that writes says so with the
75
+ response header `x-ic-upgrade` (any value); the query upgrades it, and the
76
+ header is never sent on.
77
+ - **`rack.input`** is a small reader of its own (`read`, `gets`, `each`,
78
+ `rewind`). For a `StringIO`, as Rack's own servers give, redefine
79
+ `RubyWasmIcRack.input`:
80
+
81
+ ```ruby
82
+ module RubyWasmIcRack
83
+ def self.input(bytes) = StringIO.new(bytes.b)
84
+ end
85
+ ```
86
+
87
+ ## On the `minimal` Build
88
+
89
+ Rack and Roda need four libraries `minimal` lacks: `Encoding::UTF_8` and its
90
+ two siblings, `date` (only because `time` requires it), `strscan` and
91
+ `stringio`. The [`ic_minimal`](../ic_minimal) gem carries pure-Ruby stand-ins
92
+ for them, for a `minimal` canister only. `ruby_wasm_ic_rack` itself needs none of them.
93
+
94
+ Carry only the files of Rack your routes load, and check every carried file's
95
+ magic comment: `minimal` knows only `utf-8`, `us-ascii` and `ascii-8bit` by
96
+ those names, and Rack 3.1's `rack/utils.rb` says `# encoding: binary`, which
97
+ traps the install. Change that word to `ascii-8bit`.
98
+
99
+ ## Not yet
100
+
101
+ - **Certified responses.** Until the canister certifies its replies, the
102
+ gateway refuses a plain query's reply on the certified domain
103
+ (`<id>.icp0.io`) and serves it on `<id>.raw.icp0.io`. An upgraded request is
104
+ served on either. So a public page is served raw, or by upgrading every
105
+ `GET` (one update per page view).
106
+ - File uploads (Tempfile) and streaming responses.
107
+
108
+ ## Development
109
+
110
+ The gem lives in the ruby_wasm_builder repository, `gems/ruby_wasm_ic_rack/`. Its specs
111
+ are in that repository's `spec/gems/`, and run with the builder's suite
112
+ (`bundle exec rspec`), including the 0.7.0 `rack_adapter` and `roda_on_minimal`
113
+ Scripts played with this gem's files in place of the Challenges' own copies.
114
+
115
+ MIT licensed.
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyWasmIcRack
4
+ VERSION = "0.1.1"
5
+ end
@@ -0,0 +1,120 @@
1
+ # ruby_wasm_ic_rack: serves a Rack app (anything answering call(env) with
2
+ # [status, headers, body]) from the IC HTTP gateway's two methods, in a Ruby
3
+ # canister packed by ruby_wasm_builder. Pure Ruby with no dependency, the same
4
+ # on the `minimal` and `full` Builds: it names no standard library, no C
5
+ # extension and no Encoding constant. rack.input is a small reader of its own
6
+ # unless the Application redefines RubyWasmIcRack.input (a StringIO, say).
7
+ #
8
+ # Taken from the builder's 0.7.0 Challenges (rack_adapter, roda_on_minimal),
9
+ # which keep their own copies; this gem moves on its own versions.
10
+ #
11
+ # A query can't keep a write, so http_request answers only the safe methods
12
+ # (GET, HEAD, OPTIONS) and asks the gateway to upgrade everything else to
13
+ # http_request_update. An app whose GET writes says so with the response
14
+ # header `x-ic-upgrade`, and the query upgrades that too; the header is never
15
+ # sent on.
16
+ require_relative "ruby_wasm_ic_rack/version"
17
+
18
+ module RubyWasmIcRack
19
+ SAFE = %w[GET HEAD OPTIONS].freeze
20
+ UPGRADE = "x-ic-upgrade"
21
+
22
+ # rack.input over the body's bytes: what Rack 3 asks of an input stream.
23
+ class Input
24
+ def initialize(bytes)
25
+ @bytes = bytes.b
26
+ @pos = 0
27
+ end
28
+
29
+ def read(length = nil, buffer = nil)
30
+ rest = @bytes.byteslice(@pos, @bytes.bytesize - @pos) || "".b
31
+ chunk = length ? rest.byteslice(0, length) : rest
32
+ @pos += chunk.bytesize
33
+ return nil if length && length.positive? && chunk.empty?
34
+
35
+ buffer ? buffer.replace(chunk) : chunk
36
+ end
37
+
38
+ def gets
39
+ return nil if @pos >= @bytes.bytesize
40
+
41
+ stop = @bytes.index("\n", @pos)
42
+ line = @bytes.byteslice(@pos, (stop ? stop + 1 : @bytes.bytesize) - @pos)
43
+ @pos += line.bytesize
44
+ line
45
+ end
46
+
47
+ def each
48
+ while (line = gets)
49
+ yield line
50
+ end
51
+ end
52
+
53
+ def rewind = @pos = 0
54
+ def close = nil
55
+ end
56
+
57
+ module_function
58
+
59
+ # rack.input over the body. An Application may redefine it (roda_on_minimal
60
+ # gives a StringIO).
61
+ def input(bytes) = Input.new(bytes)
62
+
63
+ def env(req)
64
+ path, query = req[:url].split("?", 2)
65
+ host = header(req, "host").to_s
66
+ env = {
67
+ "REQUEST_METHOD" => req[:method],
68
+ "SCRIPT_NAME" => "",
69
+ "PATH_INFO" => path.to_s.empty? ? "/" : path,
70
+ "QUERY_STRING" => query.to_s,
71
+ "SERVER_NAME" => host.split(":").first.to_s,
72
+ "SERVER_PORT" => "443",
73
+ "SERVER_PROTOCOL" => "HTTP/1.1",
74
+ "rack.url_scheme" => "https",
75
+ "rack.input" => input(req[:body]),
76
+ "rack.errors" => $stderr
77
+ }
78
+ req[:headers].each do |name, value|
79
+ key = name.upcase.tr("-", "_")
80
+ key = "HTTP_#{key}" unless %w[CONTENT_TYPE CONTENT_LENGTH].include?(key)
81
+ env[key] = env.key?(key) ? "#{env[key]}, #{value}" : value
82
+ end
83
+ env
84
+ end
85
+
86
+ def header(req, name) = req[:headers].find { |n, _| n.downcase == name }&.last
87
+
88
+ # The gateway's response record from a Rack response. A header's value may
89
+ # be an Array, or lines joined by "\n" (Rack 3): one header each.
90
+ def response(status, headers, body)
91
+ pairs = headers.flat_map do |name, value|
92
+ Array(value).flat_map { |v| v.to_s.split("\n") }.map { |v| [name.to_s.downcase, v] }
93
+ end
94
+ bytes = +"".b
95
+ body.each { |part| bytes << part.b }
96
+ { status_code: Integer(status), headers: pairs, body: bytes, upgrade: nil }
97
+ ensure
98
+ body.close if body.respond_to?(:close)
99
+ end
100
+
101
+ def upgrade = { status_code: 200, headers: [], body: "".b, upgrade: true }
102
+
103
+ # http_request: the query.
104
+ def query(app, req)
105
+ return upgrade unless SAFE.include?(req[:method])
106
+
107
+ status, headers, body = app.call(env(req))
108
+ if headers.keys.any? { |k| k.to_s.downcase == UPGRADE }
109
+ body.close if body.respond_to?(:close)
110
+ return upgrade
111
+ end
112
+ response(status, headers, body)
113
+ end
114
+
115
+ # http_request_update: the update, after an upgrade.
116
+ def update(app, req)
117
+ status, headers, body = app.call(env(req))
118
+ response(status, headers.reject { |k, _| k.to_s.downcase == UPGRADE }, body)
119
+ end
120
+ end
metadata ADDED
@@ -0,0 +1,45 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby_wasm_ic_rack
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - graial
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: Use this gem to run rack and roda on the ruby.wasm build for Internet
13
+ Computer.
14
+ email:
15
+ - graial@paglalayag.net
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - LICENSE
21
+ - README.md
22
+ - lib/ruby_wasm_ic_rack.rb
23
+ - lib/ruby_wasm_ic_rack/version.rb
24
+ homepage: https://github.com/graial/ruby_wasm_ic_rack
25
+ licenses:
26
+ - MIT
27
+ metadata: {}
28
+ rdoc_options: []
29
+ require_paths:
30
+ - lib
31
+ required_ruby_version: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: 3.3.3
36
+ required_rubygems_version: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ requirements: []
42
+ rubygems_version: 3.6.7
43
+ specification_version: 4
44
+ summary: Run rack and roda on the ruby.wasm build for IC!
45
+ test_files: []