tipi 0.38 → 0.42
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 +4 -4
 - data/.github/workflows/test.yml +5 -1
 - data/.gitignore +5 -0
 - data/CHANGELOG.md +34 -0
 - data/Gemfile +5 -1
 - data/Gemfile.lock +58 -16
 - data/Rakefile +7 -3
 - data/TODO.md +77 -1
 - data/benchmarks/bm_http1_parser.rb +61 -0
 - data/bin/benchmark +37 -0
 - data/bin/h1pd +6 -0
 - data/bin/tipi +3 -21
 - data/df/sample_agent.rb +1 -1
 - data/df/server.rb +16 -47
 - data/df/server_utils.rb +178 -0
 - data/examples/full_service.rb +13 -0
 - data/examples/http1_parser.rb +55 -0
 - data/examples/http_server.rb +15 -3
 - data/examples/http_server_forked.rb +5 -1
 - data/examples/http_server_routes.rb +29 -0
 - data/examples/http_server_static.rb +26 -0
 - data/examples/http_server_throttled.rb +3 -2
 - data/examples/https_server.rb +6 -4
 - data/examples/https_wss_server.rb +2 -1
 - data/examples/rack_server.rb +5 -0
 - data/examples/rack_server_https.rb +1 -1
 - data/examples/rack_server_https_forked.rb +4 -3
 - data/examples/routing_server.rb +5 -4
 - data/examples/servername_cb.rb +37 -0
 - data/examples/websocket_demo.rb +2 -8
 - data/examples/ws_page.html +2 -2
 - data/ext/tipi/extconf.rb +13 -0
 - data/ext/tipi/http1_parser.c +823 -0
 - data/ext/tipi/http1_parser.h +18 -0
 - data/ext/tipi/tipi_ext.c +5 -0
 - data/lib/tipi.rb +89 -1
 - data/lib/tipi/acme.rb +308 -0
 - data/lib/tipi/cli.rb +30 -0
 - data/lib/tipi/digital_fabric/agent.rb +22 -17
 - data/lib/tipi/digital_fabric/agent_proxy.rb +95 -40
 - data/lib/tipi/digital_fabric/executive.rb +6 -2
 - data/lib/tipi/digital_fabric/protocol.rb +87 -15
 - data/lib/tipi/digital_fabric/request_adapter.rb +6 -10
 - data/lib/tipi/digital_fabric/service.rb +77 -51
 - data/lib/tipi/http1_adapter.rb +116 -117
 - data/lib/tipi/http2_adapter.rb +56 -10
 - data/lib/tipi/http2_stream.rb +106 -53
 - data/lib/tipi/rack_adapter.rb +2 -53
 - data/lib/tipi/response_extensions.rb +17 -0
 - data/lib/tipi/version.rb +1 -1
 - data/security/http1.rb +12 -0
 - data/test/helper.rb +60 -11
 - data/test/test_http1_parser.rb +586 -0
 - data/test/test_http_server.rb +0 -27
 - data/test/test_request.rb +1 -28
 - data/tipi.gemspec +11 -5
 - metadata +96 -22
 - data/e +0 -0
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 82155f4b86223d3fb32dc8c314abfeb75fda951747e63832ed5c15bc3b1f43cc
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 721b98fb8d330d1bc38f8f236f82ddd909235d31f5875c1b4f89895e4e3926da
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: 10389779975234d90c968626b4f9d6b168491f9a32ecf1be71683c37cf11f58f1bac52a36478691e72328236a16d309a8462104bda556772e25cebe52b2b0d53
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 99db2c15278cdfc286ffad05f266f5d62eec6a3bb1363f8dfa4c302549a308a691496ee60ec085ede59e3b594533e8e196bbca144878e5a6dccf2ecfc962151c
         
     | 
    
        data/.github/workflows/test.yml
    CHANGED
    
    | 
         @@ -22,6 +22,10 @@ jobs: 
     | 
|
| 
       22 
22 
     | 
    
         
             
                - name: Install dependencies
         
     | 
| 
       23 
23 
     | 
    
         
             
                  run: |
         
     | 
| 
       24 
24 
     | 
    
         
             
                    gem install bundler
         
     | 
| 
       25 
     | 
    
         
            -
                    bundle install
         
     | 
| 
      
 25 
     | 
    
         
            +
                    POLYPHONY_USE_LIBEV=1 bundle install
         
     | 
| 
      
 26 
     | 
    
         
            +
                - name: Show Linux kernel version
         
     | 
| 
      
 27 
     | 
    
         
            +
                  run: uname -r
         
     | 
| 
      
 28 
     | 
    
         
            +
                - name: Compile C-extension
         
     | 
| 
      
 29 
     | 
    
         
            +
                  run: bundle exec rake compile
         
     | 
| 
       26 
30 
     | 
    
         
             
                - name: Run tests
         
     | 
| 
       27 
31 
     | 
    
         
             
                  run:  bundle exec rake test
         
     | 
    
        data/.gitignore
    CHANGED
    
    
    
        data/CHANGELOG.md
    CHANGED
    
    | 
         @@ -1,3 +1,37 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            ## 0.42 2021-08-16
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            - HTTP/1 parser:  disable UTF-8 parsing for all but header values
         
     | 
| 
      
 4 
     | 
    
         
            +
            - Add support for parsing HTTP/1 from callable source
         
     | 
| 
      
 5 
     | 
    
         
            +
            - Introduce full_service API for automatic HTTPS
         
     | 
| 
      
 6 
     | 
    
         
            +
            - Introduce automatic SSL certificate provisioning
         
     | 
| 
      
 7 
     | 
    
         
            +
            - Improve handling of exceptions
         
     | 
| 
      
 8 
     | 
    
         
            +
            - Various fixes to DF service and agent pxoy
         
     | 
| 
      
 9 
     | 
    
         
            +
            - Fix upgrading to HTTP2 with a request body
         
     | 
| 
      
 10 
     | 
    
         
            +
            - Switch to new HTTP/1 parser
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            ## 0.41 2021-07-26
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            - Fix Rack adapter (#11)
         
     | 
| 
      
 15 
     | 
    
         
            +
            - Introduce experimental HTTP/1 parser
         
     | 
| 
      
 16 
     | 
    
         
            +
            - More work on DF server
         
     | 
| 
      
 17 
     | 
    
         
            +
            - Allow setting chunk size in `#respond_from_io`
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
            ## 0.40 2021-06-24
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
            - Implement serving static files using splice_chunks (nice performance boost for
         
     | 
| 
      
 22 
     | 
    
         
            +
              files bigger than 1M)
         
     | 
| 
      
 23 
     | 
    
         
            +
            - Call shutdown before closing socket
         
     | 
| 
      
 24 
     | 
    
         
            +
            - Fix examples (thanks @timhatch!)
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
            ## 0.39 2021-06-20
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
            - More work on DF server
         
     | 
| 
      
 29 
     | 
    
         
            +
            - Fix HTTP2StreamHandler#send_headers
         
     | 
| 
      
 30 
     | 
    
         
            +
            - Various fixes to HTTP/2 adapter
         
     | 
| 
      
 31 
     | 
    
         
            +
            - Fix host detection for HTTP/2 connections
         
     | 
| 
      
 32 
     | 
    
         
            +
            - Fix HTTP/1 adapter #respond with nil body
         
     | 
| 
      
 33 
     | 
    
         
            +
            - Fix HTTP1Adapter#send_headers
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
       1 
35 
     | 
    
         
             
            ## 0.38 2021-03-09
         
     | 
| 
       2 
36 
     | 
    
         | 
| 
       3 
37 
     | 
    
         
             
            - Don't use chunked transfer encoding for non-streaming responses
         
     | 
    
        data/Gemfile
    CHANGED
    
    
    
        data/Gemfile.lock
    CHANGED
    
    | 
         @@ -1,39 +1,77 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            PATH
         
     | 
| 
      
 2 
     | 
    
         
            +
              remote: ../polyphony
         
     | 
| 
      
 3 
     | 
    
         
            +
              specs:
         
     | 
| 
      
 4 
     | 
    
         
            +
                polyphony (0.69)
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            PATH
         
     | 
| 
      
 7 
     | 
    
         
            +
              remote: ../qeweney
         
     | 
| 
      
 8 
     | 
    
         
            +
              specs:
         
     | 
| 
      
 9 
     | 
    
         
            +
                qeweney (0.14)
         
     | 
| 
      
 10 
     | 
    
         
            +
                  escape_utils (~> 1.2.1)
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
       1 
12 
     | 
    
         
             
            PATH
         
     | 
| 
       2 
13 
     | 
    
         
             
              remote: .
         
     | 
| 
       3 
14 
     | 
    
         
             
              specs:
         
     | 
| 
       4 
     | 
    
         
            -
                tipi (0. 
     | 
| 
       5 
     | 
    
         
            -
                   
     | 
| 
       6 
     | 
    
         
            -
                   
     | 
| 
      
 15 
     | 
    
         
            +
                tipi (0.42)
         
     | 
| 
      
 16 
     | 
    
         
            +
                  acme-client (~> 2.0.8)
         
     | 
| 
      
 17 
     | 
    
         
            +
                  extralite (~> 1.2)
         
     | 
| 
      
 18 
     | 
    
         
            +
                  http-2 (~> 0.11)
         
     | 
| 
      
 19 
     | 
    
         
            +
                  localhost (~> 1.1.4)
         
     | 
| 
       7 
20 
     | 
    
         
             
                  msgpack (~> 1.4.2)
         
     | 
| 
       8 
     | 
    
         
            -
                  polyphony (~> 0. 
     | 
| 
       9 
     | 
    
         
            -
                  qeweney (~> 0. 
     | 
| 
      
 21 
     | 
    
         
            +
                  polyphony (~> 0.69)
         
     | 
| 
      
 22 
     | 
    
         
            +
                  qeweney (~> 0.14)
         
     | 
| 
       10 
23 
     | 
    
         
             
                  rack (>= 2.0.8, < 2.3.0)
         
     | 
| 
       11 
24 
     | 
    
         
             
                  websocket (~> 1.2.8)
         
     | 
| 
       12 
25 
     | 
    
         | 
| 
       13 
26 
     | 
    
         
             
            GEM
         
     | 
| 
       14 
27 
     | 
    
         
             
              remote: https://rubygems.org/
         
     | 
| 
       15 
28 
     | 
    
         
             
              specs:
         
     | 
| 
      
 29 
     | 
    
         
            +
                acme-client (2.0.8)
         
     | 
| 
      
 30 
     | 
    
         
            +
                  faraday (>= 0.17, < 2.0.0)
         
     | 
| 
       16 
31 
     | 
    
         
             
                ansi (1.5.0)
         
     | 
| 
       17 
32 
     | 
    
         
             
                builder (3.2.4)
         
     | 
| 
       18 
     | 
    
         
            -
                 
     | 
| 
      
 33 
     | 
    
         
            +
                cuba (3.9.3)
         
     | 
| 
      
 34 
     | 
    
         
            +
                  rack (>= 1.6.0)
         
     | 
| 
      
 35 
     | 
    
         
            +
                docile (1.4.0)
         
     | 
| 
       19 
36 
     | 
    
         
             
                escape_utils (1.2.1)
         
     | 
| 
       20 
     | 
    
         
            -
                 
     | 
| 
       21 
     | 
    
         
            -
                 
     | 
| 
       22 
     | 
    
         
            -
             
     | 
| 
       23 
     | 
    
         
            -
             
     | 
| 
      
 37 
     | 
    
         
            +
                extralite (1.2)
         
     | 
| 
      
 38 
     | 
    
         
            +
                faraday (1.7.0)
         
     | 
| 
      
 39 
     | 
    
         
            +
                  faraday-em_http (~> 1.0)
         
     | 
| 
      
 40 
     | 
    
         
            +
                  faraday-em_synchrony (~> 1.0)
         
     | 
| 
      
 41 
     | 
    
         
            +
                  faraday-excon (~> 1.1)
         
     | 
| 
      
 42 
     | 
    
         
            +
                  faraday-httpclient (~> 1.0.1)
         
     | 
| 
      
 43 
     | 
    
         
            +
                  faraday-net_http (~> 1.0)
         
     | 
| 
      
 44 
     | 
    
         
            +
                  faraday-net_http_persistent (~> 1.1)
         
     | 
| 
      
 45 
     | 
    
         
            +
                  faraday-patron (~> 1.0)
         
     | 
| 
      
 46 
     | 
    
         
            +
                  faraday-rack (~> 1.0)
         
     | 
| 
      
 47 
     | 
    
         
            +
                  multipart-post (>= 1.2, < 3)
         
     | 
| 
      
 48 
     | 
    
         
            +
                  ruby2_keywords (>= 0.0.4)
         
     | 
| 
      
 49 
     | 
    
         
            +
                faraday-em_http (1.0.0)
         
     | 
| 
      
 50 
     | 
    
         
            +
                faraday-em_synchrony (1.0.0)
         
     | 
| 
      
 51 
     | 
    
         
            +
                faraday-excon (1.1.0)
         
     | 
| 
      
 52 
     | 
    
         
            +
                faraday-httpclient (1.0.1)
         
     | 
| 
      
 53 
     | 
    
         
            +
                faraday-net_http (1.0.1)
         
     | 
| 
      
 54 
     | 
    
         
            +
                faraday-net_http_persistent (1.2.0)
         
     | 
| 
      
 55 
     | 
    
         
            +
                faraday-patron (1.0.0)
         
     | 
| 
      
 56 
     | 
    
         
            +
                faraday-rack (1.0.0)
         
     | 
| 
      
 57 
     | 
    
         
            +
                http-2 (0.11.0)
         
     | 
| 
      
 58 
     | 
    
         
            +
                http_parser.rb (0.7.0)
         
     | 
| 
      
 59 
     | 
    
         
            +
                json (2.5.1)
         
     | 
| 
      
 60 
     | 
    
         
            +
                localhost (1.1.8)
         
     | 
| 
       24 
61 
     | 
    
         
             
                minitest (5.11.3)
         
     | 
| 
       25 
     | 
    
         
            -
                minitest-reporters (1.4. 
     | 
| 
      
 62 
     | 
    
         
            +
                minitest-reporters (1.4.3)
         
     | 
| 
       26 
63 
     | 
    
         
             
                  ansi
         
     | 
| 
       27 
64 
     | 
    
         
             
                  builder
         
     | 
| 
       28 
65 
     | 
    
         
             
                  minitest (>= 5.0)
         
     | 
| 
       29 
66 
     | 
    
         
             
                  ruby-progressbar
         
     | 
| 
       30 
67 
     | 
    
         
             
                msgpack (1.4.2)
         
     | 
| 
       31 
     | 
    
         
            -
                 
     | 
| 
       32 
     | 
    
         
            -
                qeweney (0.7.5)
         
     | 
| 
       33 
     | 
    
         
            -
                  escape_utils (~> 1.2.1)
         
     | 
| 
      
 68 
     | 
    
         
            +
                multipart-post (2.1.1)
         
     | 
| 
       34 
69 
     | 
    
         
             
                rack (2.2.3)
         
     | 
| 
       35 
70 
     | 
    
         
             
                rake (12.3.3)
         
     | 
| 
       36 
     | 
    
         
            -
                 
     | 
| 
      
 71 
     | 
    
         
            +
                rake-compiler (1.1.1)
         
     | 
| 
      
 72 
     | 
    
         
            +
                  rake
         
     | 
| 
      
 73 
     | 
    
         
            +
                ruby-progressbar (1.11.0)
         
     | 
| 
      
 74 
     | 
    
         
            +
                ruby2_keywords (0.0.5)
         
     | 
| 
       37 
75 
     | 
    
         
             
                simplecov (0.17.1)
         
     | 
| 
       38 
76 
     | 
    
         
             
                  docile (~> 1.1)
         
     | 
| 
       39 
77 
     | 
    
         
             
                  json (>= 1.8, < 3)
         
     | 
| 
         @@ -45,10 +83,14 @@ PLATFORMS 
     | 
|
| 
       45 
83 
     | 
    
         
             
              ruby
         
     | 
| 
       46 
84 
     | 
    
         | 
| 
       47 
85 
     | 
    
         
             
            DEPENDENCIES
         
     | 
| 
       48 
     | 
    
         
            -
               
     | 
| 
      
 86 
     | 
    
         
            +
              cuba (~> 3.9.3)
         
     | 
| 
      
 87 
     | 
    
         
            +
              http_parser.rb (= 0.7.0)
         
     | 
| 
       49 
88 
     | 
    
         
             
              minitest (~> 5.11.3)
         
     | 
| 
       50 
89 
     | 
    
         
             
              minitest-reporters (~> 1.4.2)
         
     | 
| 
      
 90 
     | 
    
         
            +
              polyphony!
         
     | 
| 
      
 91 
     | 
    
         
            +
              qeweney!
         
     | 
| 
       51 
92 
     | 
    
         
             
              rake (~> 12.3.3)
         
     | 
| 
      
 93 
     | 
    
         
            +
              rake-compiler (= 1.1.1)
         
     | 
| 
       52 
94 
     | 
    
         
             
              simplecov (~> 0.17.1)
         
     | 
| 
       53 
95 
     | 
    
         
             
              tipi!
         
     | 
| 
       54 
96 
     | 
    
         | 
    
        data/Rakefile
    CHANGED
    
    | 
         @@ -3,10 +3,14 @@ 
     | 
|
| 
       3 
3 
     | 
    
         
             
            require "bundler/gem_tasks"
         
     | 
| 
       4 
4 
     | 
    
         
             
            require "rake/clean"
         
     | 
| 
       5 
5 
     | 
    
         | 
| 
       6 
     | 
    
         
            -
             
     | 
| 
      
 6 
     | 
    
         
            +
            require "rake/extensiontask"
         
     | 
| 
      
 7 
     | 
    
         
            +
            Rake::ExtensionTask.new("tipi_ext") do |ext|
         
     | 
| 
      
 8 
     | 
    
         
            +
              ext.ext_dir = "ext/tipi"
         
     | 
| 
      
 9 
     | 
    
         
            +
            end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            task :recompile => [:clean, :compile]
         
     | 
| 
      
 12 
     | 
    
         
            +
            task :default => [:compile, :test]
         
     | 
| 
       7 
13 
     | 
    
         | 
| 
       8 
     | 
    
         
            -
            task :default => [:test]
         
     | 
| 
       9 
14 
     | 
    
         
             
            task :test do
         
     | 
| 
       10 
15 
     | 
    
         
             
              exec 'ruby test/run.rb'
         
     | 
| 
       11 
16 
     | 
    
         
             
            end
         
     | 
| 
       12 
     | 
    
         
            -
             
     | 
    
        data/TODO.md
    CHANGED
    
    | 
         @@ -1,4 +1,80 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
             
     | 
| 
      
 1 
     | 
    
         
            +
            ## Add an API for reading a request body chunk into an IO (pipe)
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
                  ```ruby
         
     | 
| 
      
 4 
     | 
    
         
            +
                  # currently
         
     | 
| 
      
 5 
     | 
    
         
            +
                  chunk = req.next_chunk
         
     | 
| 
      
 6 
     | 
    
         
            +
                  # or
         
     | 
| 
      
 7 
     | 
    
         
            +
                  req.each_chunk { |c| do_something(c) }
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                  # what we'd like to do
         
     | 
| 
      
 10 
     | 
    
         
            +
                  r, w = IO.pipe
         
     | 
| 
      
 11 
     | 
    
         
            +
                  len = req.splice_chunk(w)
         
     | 
| 
      
 12 
     | 
    
         
            +
                  sock << "Here comes a chunk of #{len} bytes\n"
         
     | 
| 
      
 13 
     | 
    
         
            +
                  sock.splice(r, len)
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  # or:
         
     | 
| 
      
 16 
     | 
    
         
            +
                  r, w = IO.pipe
         
     | 
| 
      
 17 
     | 
    
         
            +
                  req.splice_each_chunk(w) do |len|
         
     | 
| 
      
 18 
     | 
    
         
            +
                    sock << "Here comes a chunk of #{len} bytes\n"
         
     | 
| 
      
 19 
     | 
    
         
            +
                    sock.splice(r, len)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  end
         
     | 
| 
      
 21 
     | 
    
         
            +
                  ```
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
            # HTTP/1.1 parser
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
            - httparser.rb is not actively updated
         
     | 
| 
      
 26 
     | 
    
         
            +
            - the httparser.rb C parser code comes originally from https://github.com/nodejs/llhttp
         
     | 
| 
      
 27 
     | 
    
         
            +
            - there's a Ruby gem https://github.com/metabahn/llhttp, but its API is too low-level
         
     | 
| 
      
 28 
     | 
    
         
            +
              (lots of callbacks, headers need to be retained across callbacks)
         
     | 
| 
      
 29 
     | 
    
         
            +
            - the basic idea is to import the C-code, then build a parser object with the following
         
     | 
| 
      
 30 
     | 
    
         
            +
              callbacks:
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
              ```ruby
         
     | 
| 
      
 33 
     | 
    
         
            +
              on_headers_complete(headers)
         
     | 
| 
      
 34 
     | 
    
         
            +
              on_body_chunk(chunk)
         
     | 
| 
      
 35 
     | 
    
         
            +
              on_message_complete
         
     | 
| 
      
 36 
     | 
    
         
            +
              ```
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
            - The llhttp gem's C-code is here: https://github.com/metabahn/llhttp/tree/main/mri
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
            - Actually, if you do a C extension, instead of a callback-based API, we can
         
     | 
| 
      
 41 
     | 
    
         
            +
              design a blocking API:
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
              ```ruby
         
     | 
| 
      
 44 
     | 
    
         
            +
              parser = Tipi::HTTP1::Parser.new
         
     | 
| 
      
 45 
     | 
    
         
            +
              parser.each_request(socket) do |headers|
         
     | 
| 
      
 46 
     | 
    
         
            +
                request = Request.new(normalize_headers(headers))
         
     | 
| 
      
 47 
     | 
    
         
            +
                handle_request(request)
         
     | 
| 
      
 48 
     | 
    
         
            +
              end
         
     | 
| 
      
 49 
     | 
    
         
            +
              ```
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
            # What about HTTP/2?
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
            It would be a nice exercise in converting a callback-based API to a blocking
         
     | 
| 
      
 54 
     | 
    
         
            +
            one:
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 57 
     | 
    
         
            +
            parser = Tipi::HTTP2::Parser.new(socket)
         
     | 
| 
      
 58 
     | 
    
         
            +
            parser.each_stream(socket) do |stream|
         
     | 
| 
      
 59 
     | 
    
         
            +
              spin { handle_stream(stream) }
         
     | 
| 
      
 60 
     | 
    
         
            +
            end
         
     | 
| 
      
 61 
     | 
    
         
            +
            ```
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
            # DF
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
            - Add attack protection for IP-address HTTP host:
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
              ```ruby
         
     | 
| 
      
 70 
     | 
    
         
            +
              IPV4_REGEXP = /^\d+\.\d+\.\d+\.\d+$/.freeze
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
              def is_attack_request?(req)
         
     | 
| 
      
 73 
     | 
    
         
            +
                return true if req.host =~ IPV4_REGEXP && req.query[:q] != 'ping'
         
     | 
| 
      
 74 
     | 
    
         
            +
              end
         
     | 
| 
      
 75 
     | 
    
         
            +
              ```
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
            - Add attack route to Qeweney routing API
         
     | 
| 
       2 
78 
     | 
    
         | 
| 
       3 
79 
     | 
    
         | 
| 
       4 
80 
     | 
    
         | 
| 
         @@ -0,0 +1,61 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require 'bundler/setup'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            HTTP_REQUEST = "GET /foo HTTP/1.1\r\nHost: example.com\r\nAccept: */*\r\n\r\n"
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            def benchmark_other_http1_parser(iterations)
         
     | 
| 
      
 8 
     | 
    
         
            +
              STDOUT << "http_parser.rb: "
         
     | 
| 
      
 9 
     | 
    
         
            +
              require 'http_parser.rb'
         
     | 
| 
      
 10 
     | 
    
         
            +
              
         
     | 
| 
      
 11 
     | 
    
         
            +
              i, o = IO.pipe
         
     | 
| 
      
 12 
     | 
    
         
            +
              parser = Http::Parser.new
         
     | 
| 
      
 13 
     | 
    
         
            +
              done = false
         
     | 
| 
      
 14 
     | 
    
         
            +
              headers = nil
         
     | 
| 
      
 15 
     | 
    
         
            +
              parser.on_headers_complete = proc do |h|
         
     | 
| 
      
 16 
     | 
    
         
            +
                headers = h
         
     | 
| 
      
 17 
     | 
    
         
            +
                headers[':method'] = parser.http_method
         
     | 
| 
      
 18 
     | 
    
         
            +
                headers[':path'] = parser.request_url
         
     | 
| 
      
 19 
     | 
    
         
            +
                headers[':protocol'] = parser.http_version
         
     | 
| 
      
 20 
     | 
    
         
            +
              end
         
     | 
| 
      
 21 
     | 
    
         
            +
              parser.on_message_complete = proc { done = true }
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
              t0 = Time.now
         
     | 
| 
      
 24 
     | 
    
         
            +
              iterations.times do
         
     | 
| 
      
 25 
     | 
    
         
            +
                o << HTTP_REQUEST
         
     | 
| 
      
 26 
     | 
    
         
            +
                done = false
         
     | 
| 
      
 27 
     | 
    
         
            +
                while !done
         
     | 
| 
      
 28 
     | 
    
         
            +
                  msg = i.readpartial(4096)
         
     | 
| 
      
 29 
     | 
    
         
            +
                  parser << msg
         
     | 
| 
      
 30 
     | 
    
         
            +
                end
         
     | 
| 
      
 31 
     | 
    
         
            +
              end
         
     | 
| 
      
 32 
     | 
    
         
            +
              t1 = Time.now
         
     | 
| 
      
 33 
     | 
    
         
            +
              puts "#{iterations / (t1 - t0)} ips"
         
     | 
| 
      
 34 
     | 
    
         
            +
            end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
            def benchmark_tipi_http1_parser(iterations)
         
     | 
| 
      
 37 
     | 
    
         
            +
              STDOUT << "tipi parser: "
         
     | 
| 
      
 38 
     | 
    
         
            +
              require_relative '../lib/tipi_ext'
         
     | 
| 
      
 39 
     | 
    
         
            +
              i, o = IO.pipe
         
     | 
| 
      
 40 
     | 
    
         
            +
              reader = proc { |len| i.readpartial(len) }
         
     | 
| 
      
 41 
     | 
    
         
            +
              parser = Tipi::HTTP1Parser.new(reader)
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
              t0 = Time.now
         
     | 
| 
      
 44 
     | 
    
         
            +
              iterations.times do
         
     | 
| 
      
 45 
     | 
    
         
            +
                o << HTTP_REQUEST
         
     | 
| 
      
 46 
     | 
    
         
            +
                headers = parser.parse_headers
         
     | 
| 
      
 47 
     | 
    
         
            +
              end
         
     | 
| 
      
 48 
     | 
    
         
            +
              t1 = Time.now
         
     | 
| 
      
 49 
     | 
    
         
            +
              puts "#{iterations / (t1 - t0)} ips"
         
     | 
| 
      
 50 
     | 
    
         
            +
            end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
            def fork_benchmark(method, iterations)
         
     | 
| 
      
 53 
     | 
    
         
            +
              pid = fork { send(method, iterations) }
         
     | 
| 
      
 54 
     | 
    
         
            +
              Process.wait(pid)
         
     | 
| 
      
 55 
     | 
    
         
            +
            end
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
            x = 500000
         
     | 
| 
      
 58 
     | 
    
         
            +
            fork_benchmark(:benchmark_other_http1_parser, x)
         
     | 
| 
      
 59 
     | 
    
         
            +
            fork_benchmark(:benchmark_tipi_http1_parser, x)
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
            # benchmark_tipi_http1_parser(x)
         
     | 
    
        data/bin/benchmark
    ADDED
    
    | 
         @@ -0,0 +1,37 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            #!/usr/bin/env ruby
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require 'bundler/setup'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'polyphony'
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            def parse_latency(latency)
         
     | 
| 
      
 7 
     | 
    
         
            +
              m = latency.match(/^([\d\.]+)(us|ms|s)$/)
         
     | 
| 
      
 8 
     | 
    
         
            +
              return nil unless m
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
              value = m[1].to_f
         
     | 
| 
      
 11 
     | 
    
         
            +
              case m[2]
         
     | 
| 
      
 12 
     | 
    
         
            +
              when 's' then value
         
     | 
| 
      
 13 
     | 
    
         
            +
              when 'ms' then value / 1000
         
     | 
| 
      
 14 
     | 
    
         
            +
              when 'us' then value / 1000000
         
     | 
| 
      
 15 
     | 
    
         
            +
              end
         
     | 
| 
      
 16 
     | 
    
         
            +
            end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
            def parse_wrk_results(results)
         
     | 
| 
      
 19 
     | 
    
         
            +
              lines = results.lines
         
     | 
| 
      
 20 
     | 
    
         
            +
              latencies = lines[3].strip.split(/\s+/)
         
     | 
| 
      
 21 
     | 
    
         
            +
              throughput = lines[6].strip.split(/\s+/)
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
              {
         
     | 
| 
      
 24 
     | 
    
         
            +
                latency_avg:  parse_latency(latencies[1]),
         
     | 
| 
      
 25 
     | 
    
         
            +
                latency_max:  parse_latency(latencies[3]),
         
     | 
| 
      
 26 
     | 
    
         
            +
                rate:         throughput[1].to_f
         
     | 
| 
      
 27 
     | 
    
         
            +
              }
         
     | 
| 
      
 28 
     | 
    
         
            +
            end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
            def run_wrk(duration: 10, threads: 2, connections: 10, url: )
         
     | 
| 
      
 31 
     | 
    
         
            +
              `wrk -d#{duration} -t#{threads} -c#{connections} #{url}`
         
     | 
| 
      
 32 
     | 
    
         
            +
            end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
            [8, 64, 256, 512].each do |c|
         
     | 
| 
      
 35 
     | 
    
         
            +
              puts "connections: #{c}"
         
     | 
| 
      
 36 
     | 
    
         
            +
              p parse_wrk_results(run_wrk(duration: 10, threads: 4, connections: c, url: "http://localhost:10080/"))
         
     | 
| 
      
 37 
     | 
    
         
            +
            end
         
     | 
    
        data/bin/h1pd
    ADDED
    
    
    
        data/bin/tipi
    CHANGED
    
    | 
         @@ -1,26 +1,8 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            #!/usr/bin/env ruby
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
3 
     | 
    
         
             
            require 'bundler/setup'
         
     | 
| 
       4 
     | 
    
         
            -
            require ' 
     | 
| 
       5 
     | 
    
         
            -
            require File.expand_path('../lib/tipi/configuration', __dir__)
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'tipi/cli'
         
     | 
| 
       6 
5 
     | 
    
         | 
| 
       7 
     | 
    
         
            -
             
     | 
| 
       8 
     | 
    
         
            -
            #config[:forked] = 4
         
     | 
| 
      
 6 
     | 
    
         
            +
            trap('SIGINT') { exit }
         
     | 
| 
       9 
7 
     | 
    
         | 
| 
       10 
     | 
    
         
            -
             
     | 
| 
       11 
     | 
    
         
            -
            puts
         
     | 
| 
       12 
     | 
    
         
            -
             
     | 
| 
       13 
     | 
    
         
            -
            configuration_manager = spin { Tipi::Configuration.supervise_config }
         
     | 
| 
       14 
     | 
    
         
            -
             
     | 
| 
       15 
     | 
    
         
            -
            configuration_manager << config
         
     | 
| 
       16 
     | 
    
         
            -
            configuration_manager.await
         
     | 
| 
       17 
     | 
    
         
            -
             
     | 
| 
       18 
     | 
    
         
            -
            __END__
         
     | 
| 
       19 
     | 
    
         
            -
             
     | 
| 
       20 
     | 
    
         
            -
                      ooo
         
     | 
| 
       21 
     | 
    
         
            -
                   oo
         
     | 
| 
       22 
     | 
    
         
            -
                 o
         
     | 
| 
       23 
     | 
    
         
            -
               \|/
         
     | 
| 
       24 
     | 
    
         
            -
               / \   Tipi - A better web server for a better world
         
     | 
| 
       25 
     | 
    
         
            -
              /___\
         
     | 
| 
       26 
     | 
    
         
            -
              
         
     | 
| 
      
 8 
     | 
    
         
            +
            Tipi::CLI.start
         
     | 
    
        data/df/sample_agent.rb
    CHANGED
    
    | 
         @@ -13,7 +13,7 @@ class SampleAgent < DigitalFabric::Agent 
     | 
|
| 
       13 
13 
     | 
    
         
             
              HTML_SSE = IO.read(File.join(__dir__, 'sse_page.html'))
         
     | 
| 
       14 
14 
     | 
    
         | 
| 
       15 
15 
     | 
    
         
             
              def http_request(req)
         
     | 
| 
       16 
     | 
    
         
            -
                path = req 
     | 
| 
      
 16 
     | 
    
         
            +
                path = req.headers[':path']
         
     | 
| 
       17 
17 
     | 
    
         
             
                case path
         
     | 
| 
       18 
18 
     | 
    
         
             
                when '/agent'
         
     | 
| 
       19 
19 
     | 
    
         
             
                  send_df_message(Protocol.http_response(
         
     | 
    
        data/df/server.rb
    CHANGED
    
    | 
         @@ -1,54 +1,23 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # frozen_string_literal: true
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
     | 
    
         
            -
             
     | 
| 
       4 
     | 
    
         
            -
            require 'tipi'
         
     | 
| 
       5 
     | 
    
         
            -
            require 'tipi/digital_fabric'
         
     | 
| 
       6 
     | 
    
         
            -
            require 'tipi/digital_fabric/executive'
         
     | 
| 
       7 
     | 
    
         
            -
            require 'json'
         
     | 
| 
       8 
     | 
    
         
            -
            require 'fileutils'
         
     | 
| 
       9 
     | 
    
         
            -
            FileUtils.cd(__dir__)
         
     | 
| 
      
 3 
     | 
    
         
            +
            require_relative 'server_utils'
         
     | 
| 
       10 
4 
     | 
    
         | 
| 
       11 
     | 
    
         
            -
             
     | 
| 
       12 
     | 
    
         
            -
             
     | 
| 
       13 
     | 
    
         
            -
             
     | 
| 
       14 
     | 
    
         
            -
             
     | 
| 
       15 
     | 
    
         
            -
             
     | 
| 
       16 
     | 
    
         
            -
            class Polyphony::BaseException
         
     | 
| 
       17 
     | 
    
         
            -
              attr_reader :caller_backtrace
         
     | 
| 
       18 
     | 
    
         
            -
            end
         
     | 
| 
       19 
     | 
    
         
            -
             
     | 
| 
       20 
     | 
    
         
            -
            puts "pid: #{Process.pid}"
         
     | 
| 
       21 
     | 
    
         
            -
             
     | 
| 
       22 
     | 
    
         
            -
            tcp_listener = spin do
         
     | 
| 
       23 
     | 
    
         
            -
              opts = {
         
     | 
| 
       24 
     | 
    
         
            -
                reuse_addr:  true,
         
     | 
| 
       25 
     | 
    
         
            -
                dont_linger: true,
         
     | 
| 
       26 
     | 
    
         
            -
              }
         
     | 
| 
       27 
     | 
    
         
            -
              puts 'Listening on localhost:4411'
         
     | 
| 
       28 
     | 
    
         
            -
              server = Polyphony::Net.tcp_listen('0.0.0.0', 4411, opts)
         
     | 
| 
       29 
     | 
    
         
            -
              server.accept_loop do |client|
         
     | 
| 
       30 
     | 
    
         
            -
                spin do
         
     | 
| 
       31 
     | 
    
         
            -
                  service.incr_connection_count
         
     | 
| 
       32 
     | 
    
         
            -
                  Tipi.client_loop(client, opts) { |req| service.http_request(req) }
         
     | 
| 
       33 
     | 
    
         
            -
                ensure
         
     | 
| 
       34 
     | 
    
         
            -
                  service.decr_connection_count
         
     | 
| 
       35 
     | 
    
         
            -
                end
         
     | 
| 
       36 
     | 
    
         
            -
              end
         
     | 
| 
       37 
     | 
    
         
            -
            end
         
     | 
| 
       38 
     | 
    
         
            -
             
     | 
| 
       39 
     | 
    
         
            -
            UNIX_SOCKET_PATH = '/tmp/df.sock'
         
     | 
| 
       40 
     | 
    
         
            -
             
     | 
| 
       41 
     | 
    
         
            -
            unix_listener = spin do
         
     | 
| 
       42 
     | 
    
         
            -
              puts "Listening on #{UNIX_SOCKET_PATH}"
         
     | 
| 
       43 
     | 
    
         
            -
              FileUtils.rm(UNIX_SOCKET_PATH) if File.exists?(UNIX_SOCKET_PATH)
         
     | 
| 
       44 
     | 
    
         
            -
              socket = UNIXServer.new(UNIX_SOCKET_PATH)
         
     | 
| 
       45 
     | 
    
         
            -
              Tipi.accept_loop(socket, {}) { |req| service.http_request(req) }
         
     | 
| 
       46 
     | 
    
         
            -
            end
         
     | 
| 
      
 5 
     | 
    
         
            +
            listeners = [
         
     | 
| 
      
 6 
     | 
    
         
            +
              listen_http,
         
     | 
| 
      
 7 
     | 
    
         
            +
              listen_https,
         
     | 
| 
      
 8 
     | 
    
         
            +
              listen_unix
         
     | 
| 
      
 9 
     | 
    
         
            +
            ]
         
     | 
| 
       47 
10 
     | 
    
         | 
| 
       48 
11 
     | 
    
         
             
            begin
         
     | 
| 
       49 
     | 
    
         
            -
               
     | 
| 
      
 12 
     | 
    
         
            +
              log('Starting DF server')
         
     | 
| 
      
 13 
     | 
    
         
            +
              Fiber.await(*listeners)
         
     | 
| 
       50 
14 
     | 
    
         
             
            rescue Interrupt
         
     | 
| 
       51 
     | 
    
         
            -
               
     | 
| 
       52 
     | 
    
         
            -
              service.graceful_shutdown
         
     | 
| 
       53 
     | 
    
         
            -
             
     | 
| 
      
 15 
     | 
    
         
            +
              log('Got SIGINT, shutting down gracefully')
         
     | 
| 
      
 16 
     | 
    
         
            +
              @service.graceful_shutdown
         
     | 
| 
      
 17 
     | 
    
         
            +
            rescue SystemExit
         
     | 
| 
      
 18 
     | 
    
         
            +
              # ignore
         
     | 
| 
      
 19 
     | 
    
         
            +
            rescue Exception => e
         
     | 
| 
      
 20 
     | 
    
         
            +
              log("Uncaught exception", error: e, source: e.source_fiber, raising: e.raising_fiber, backtrace: e.backtrace)
         
     | 
| 
      
 21 
     | 
    
         
            +
            ensure
         
     | 
| 
      
 22 
     | 
    
         
            +
              log('DF server stopped')
         
     | 
| 
       54 
23 
     | 
    
         
             
            end
         
     |