proxo 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 9b0790fc6dcd6f7d999b10bae2403f876d4d4a0a89cce3c14306b10f621ec370
4
+ data.tar.gz: b71aaad1fc3617318a43576679067875859f03e63ccee11f87554ee37e8fd4bd
5
+ SHA512:
6
+ metadata.gz: 93ccc36f23c2b79abc9e9f88ea4f6960e7a2da0cf0a158044b0bae0d00363241bc836232b0bc389394baa2389f21878afba95490bdea163513cfdcc44078ce5d
7
+ data.tar.gz: 30dbc8c9aa697e4ee2cb36005390046c30a928882a7cd322602086b94bfcdb5eadbffd6e7f84e42b94ea5fc57e50c995b8b0609319d2e37d7438ef4a1a24a252
@@ -0,0 +1,16 @@
1
+ name: Ruby
2
+
3
+ on: [push,pull_request]
4
+
5
+ jobs:
6
+ build:
7
+ runs-on: ubuntu-latest
8
+ steps:
9
+ - uses: actions/checkout@v2
10
+ - name: Set up Ruby
11
+ uses: ruby/setup-ruby@v1
12
+ with:
13
+ ruby-version: 2.6.7
14
+ bundler-cache: true
15
+ - name: Run the default task
16
+ run: bundle exec rake
data/.gitignore ADDED
@@ -0,0 +1,70 @@
1
+ # generic stuff
2
+ .env
3
+ *.gem
4
+ *.rbc
5
+ log/*.log
6
+ /.config
7
+ /InstalledFiles
8
+ /pkg/
9
+ /tmp/
10
+
11
+ # testy stuff
12
+ .rspec
13
+ .rspec_status
14
+ *.orig
15
+ /coverage/
16
+ /coverage/
17
+ /db/*.sqlite3
18
+ /db/*.sqlite3-[0-9]*
19
+ /db/*.sqlite3-journal
20
+ /public/system
21
+ /spec/examples.txt
22
+ /spec/reports/
23
+ /spec/tmp
24
+ /test/tmp/
25
+ /test/version_tmp/
26
+ capybara-*.html
27
+ pickle-email-*.html
28
+ rerun.txt
29
+ test/dummy/db/*.sqlite3
30
+ test/dummy/db/*.sqlite3-journal
31
+ test/dummy/log/*.log
32
+ test/dummy/node_modules/
33
+ test/dummy/storage/
34
+ test/dummy/tmp/
35
+ test/dummy/yarn-error.log
36
+
37
+ # debuggy stuff
38
+ .byebug_history
39
+
40
+ ## doccy stuff
41
+ /.yardoc/
42
+ /_yardoc/
43
+ /doc/
44
+ /rdoc/
45
+
46
+ ## bundly stuff
47
+ /.bundle/
48
+ /vendor/bundle
49
+ /lib/bundler/man/
50
+
51
+ # "for a library or gem, you might want to ignore these files since the code is
52
+ # intended to run in multiple environments"
53
+ Gemfile.lock
54
+ .ruby-version
55
+ .ruby-gemset
56
+
57
+ # rvmmy stuff
58
+ .rvmrc
59
+
60
+ # editory stuff
61
+ .idea
62
+ .vscode
63
+ *.rdb
64
+
65
+ # systemy stuff
66
+ *.swm
67
+ *.swn
68
+ *.swo
69
+ *.swp
70
+ *.DS_Store
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,13 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.6
3
+
4
+ Style/StringLiterals:
5
+ Enabled: true
6
+ EnforcedStyle: double_quotes
7
+
8
+ Style/StringLiteralsInInterpolation:
9
+ Enabled: true
10
+ EnforcedStyle: double_quotes
11
+
12
+ Layout/LineLength:
13
+ Max: 120
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2021-07-29
4
+
5
+ - Initial release
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in proxo.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+
10
+ gem "rspec", "~> 3.0"
11
+
12
+ gem "rubocop", "~> 1.7"
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 Keegan Leitz
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
13
+ all 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
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,105 @@
1
+ # Proxo
2
+
3
+ Proxy one port to another port, and intercept/transform/log inbound and outbound TCP messages.
4
+
5
+ ## Installation
6
+
7
+ ### In your application
8
+
9
+ Add this line to your application's `Gemfile`:
10
+
11
+ ```ruby
12
+ gem 'proxo'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ ```sh
18
+ bundle install
19
+ ```
20
+
21
+ ### Globally (to your system)
22
+
23
+ Or install it globally:
24
+
25
+ ```sh
26
+ gem install proxo
27
+ ```
28
+
29
+ ## Usage
30
+
31
+ ### On the command line
32
+
33
+ #### Option flags
34
+
35
+ ```
36
+ > proxo --help
37
+ Usage: proxo [options]
38
+ -h, --help Show this help message
39
+ -v, --verbose Log all data received and republished, as well as lifecycle events
40
+ -i, --input-host INPUT_HOST Host to listen to (default: 127.0.0.1)
41
+ -p, --input-port INPUT_PORT Port to listen on (required)
42
+ -o, --output-host OUTPUT_HOST Host to republish to (default: 127.0.0.1)
43
+ -q, --output-port OUTPUT_PORT Port to republish to (will NOT republish if no output port is given)
44
+ -l, --log LOG_FILE File to log to (default: logs to STDOUT)
45
+ ```
46
+
47
+ #### Common examples
48
+
49
+ Listen to all messages and activity sent to port 5000:
50
+
51
+ ```bash
52
+ proxo --verbose --input-port 5000
53
+ ```
54
+
55
+ Proxy messages from port 5000 to 8080, and log the contents of all messages and activity:
56
+
57
+ ```bash
58
+ proxo --verbose --input-port 5000 --output-port 8080
59
+ ```
60
+
61
+ ### In an application
62
+
63
+ ```rb
64
+ require "proxo"
65
+ require "json"
66
+ require "logger"
67
+
68
+ logger = Logger.new("log/log_file.log")
69
+ logger.level = Logger::INFO
70
+
71
+ proxy = Proxo::Proxomaton.new(
72
+ input_port: 5000,
73
+ output_port: 8080,
74
+ verbose: true,
75
+ logger: logger
76
+ )
77
+
78
+ proxy.on_the_way_there do |data|
79
+ puts "I'm sending this data: #{data}."
80
+ puts "I'm also throttling this request."
81
+ sleep 0.5
82
+
83
+ puts "I'm also remembering to return data to send to the output port."
84
+ puts "If I didn't return any data, the output port wouldn't receive any."
85
+ data
86
+ end
87
+
88
+ proxy.on_the_way_back do |data|
89
+ puts "I'm adding an unexpected field to the data coming back from the"
90
+ puts "application running on the output port."
91
+ payload = JSON.parse(data)
92
+ payload["foobar"] = "whoa"
93
+ payload.to_json
94
+ end
95
+
96
+ proxy.start!
97
+ ```
98
+
99
+ ## Contributing
100
+
101
+ Bug reports and pull requests are welcome on GitHub at https://github.com/kjleitz/proxo.
102
+
103
+ ## License
104
+
105
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "proxo"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require "irb"
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/exe/proxo ADDED
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "proxo"
4
+ require "optparse"
5
+
6
+ options = {}
7
+
8
+ arguments_parser = OptionParser.new do |opts|
9
+ opts.banner = "Usage: proxo [options]"
10
+
11
+ opts.on("-h", "--help", "Show this help message") do
12
+ puts opts
13
+ exit
14
+ end
15
+
16
+ opts.on("-v", "--verbose", TrueClass,
17
+ "Log all data received and republished, as well as lifecycle events") do |verbose|
18
+ options[:verbose] = verbose
19
+ end
20
+
21
+ opts.on("-iINPUT_HOST", "--input-host INPUT_HOST", "Host to listen to (default: 127.0.0.1)") do |input_host|
22
+ options[:input_host] = input_host
23
+ end
24
+
25
+ opts.on(:REQUIRED, "-pINPUT_PORT", "--input-port INPUT_PORT", Integer, "Port to listen on (required)") do |input_port|
26
+ options[:input_port] = input_port
27
+ end
28
+
29
+ opts.on("-oOUTPUT_HOST", "--output-host OUTPUT_HOST", "Host to republish to (default: 127.0.0.1)") do |output_host|
30
+ options[:output_host] = output_host
31
+ end
32
+
33
+ opts.on("-qOUTPUT_PORT", "--output-port OUTPUT_PORT", Integer,
34
+ "Port to republish to (will NOT republish if no output port is given)") do |output_port|
35
+ options[:output_port] = output_port
36
+ end
37
+
38
+ opts.on("-lLOG_FILE", "--log LOG_FILE", "File to log to (default: logs to STDOUT)") do |log_file|
39
+ logger = Logger.new(log_file)
40
+ logger.level = Logger::INFO
41
+ options[:logger] = logger
42
+ end
43
+ end
44
+
45
+ arguments_parser.parse!
46
+
47
+ raise OptionParser::MissingArgument, "--input-port (see --help for details)" if options[:input_port].nil?
48
+
49
+ proxomaton = Proxo::Proxomaton.new(**options)
50
+
51
+ proxomaton.start!
data/lib/proxo.rb ADDED
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "socket"
4
+ require "logger"
5
+
6
+ require_relative "proxo/version"
7
+
8
+ module Proxo
9
+ class Error < StandardError; end
10
+ # Your code goes here...
11
+
12
+ class Proxomaton
13
+ VALID_LOG_LEVELS = %i[unknown fatal error warn info].freeze
14
+
15
+ attr_reader(*%i[
16
+ input_host
17
+ input_port
18
+ output_host
19
+ output_port
20
+ logger
21
+ outbound_middleman
22
+ inbound_middleman
23
+ ])
24
+
25
+ def initialize(input_port:, input_host: "127.0.0.1", output_host: nil, output_port: nil, verbose: false, logger: nil)
26
+ # The host to listen to
27
+ @input_host = input_host
28
+
29
+ # The port to listen on
30
+ @input_port = input_port.to_i
31
+
32
+ # The host to publish to
33
+ @output_host = output_host || input_host
34
+
35
+ # The port to publish to (`nil` if you don't want to republish)
36
+ @output_port = output_port.to_i unless output_port.nil?
37
+
38
+ # Print all received data before running middleman, and some other info
39
+ @verbose = !!verbose
40
+
41
+ # Logger for printing (defaults to STDOUT)
42
+ @logger = logger || Logger.new(STDOUT)
43
+ @logger.level = Logger::INFO
44
+
45
+ # If no middlemen are given, it'll just pass the data through unmodified
46
+ @outbound_middleman = proc(&:itself)
47
+ @inbound_middleman = proc(&:itself)
48
+ end
49
+
50
+ # The given block will be called when data is received from the "input"
51
+ # socket. The block takes one argument: the data received.
52
+ def on_the_way_there(&block)
53
+ @outbound_middleman = block
54
+ end
55
+
56
+ # The given block will be called when data is received back from the
57
+ # "output" socket. The block takes one argument: the data received.
58
+ def on_the_way_back(&block)
59
+ @inbound_middleman = block
60
+ end
61
+
62
+ def start!
63
+ Thread.abort_on_exception = true
64
+
65
+ input_server = TCPServer.new(input_host, input_port)
66
+
67
+ loop do
68
+ Thread.start(input_server.accept) do |input_socket|
69
+ output_socket = TCPSocket.new(output_host, output_port)
70
+
71
+ loop do
72
+ # Waiting for sockets to be ready...
73
+ ready_sockets, * = IO.select([input_socket, output_socket], nil, nil)
74
+
75
+ if ready_sockets && ready_sockets.include?(input_socket)
76
+ # Waiting for output to be writable...
77
+ _, write_sockets, * = IO.select(nil, [output_socket], nil, 0)
78
+
79
+ if write_sockets && write_sockets.include?(output_socket)
80
+ # Receiving data from input port...
81
+ data = input_socket.gets
82
+ break if data.nil?
83
+
84
+ log("Received data from input port: #{data.chomp}")
85
+ new_data = outbound_middleman.call(data)
86
+ log("Writing transformed data to output port: #{new_data.chomp}")
87
+ output_socket.write(new_data)
88
+ end
89
+ end
90
+
91
+ next unless ready_sockets && ready_sockets.include?(output_socket)
92
+
93
+ # Waiting for input to be writable...
94
+ _, write_sockets, * = IO.select(nil, [input_socket], nil, 0)
95
+
96
+ next unless write_sockets && write_sockets.include?(input_socket)
97
+
98
+ # Receiving data from output port...
99
+ data = output_socket.gets
100
+ break if data.nil?
101
+
102
+ log("Received data from output port: #{data.chomp}")
103
+ new_data = inbound_middleman.call(data)
104
+ log("Writing transformed data to input port: #{new_data.chomp}")
105
+ input_socket.write(new_data)
106
+ end
107
+
108
+ input_socket.close
109
+ output_socket.close
110
+ end
111
+ end
112
+ end
113
+
114
+ private
115
+
116
+ def verbose?
117
+ @verbose
118
+ end
119
+
120
+ def should_republish?
121
+ !output_port.nil?
122
+ end
123
+
124
+ def log(message, level: :info)
125
+ return unless verbose?
126
+
127
+ log_level = level.to_s.downcase.to_sym
128
+
129
+ unless VALID_LOG_LEVELS.include?(level.to_sym)
130
+ raise ArgumentError,
131
+ "Invalid log level '#{level}'. Valid levels are #{listify(VALID_LOG_LEVELS, connect: "or")}"
132
+ end
133
+
134
+ logger.public_send(level, message)
135
+ end
136
+
137
+ def listify(raw_list, connect: "and")
138
+ list = raw_list.reduce([]) do |memo, item|
139
+ item_string = item.to_s.strip
140
+ item_string.empty? ? memo : [*memo, item]
141
+ end
142
+
143
+ return "" if list.empty?
144
+ return list.first if list.count == 1
145
+ return list.join(connect) if list.count == 2
146
+
147
+ *first_items, last_item = list
148
+ "#{first_items.join(",")}, #{connect} #{last_item}"
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Proxo
4
+ VERSION = "0.1.0"
5
+ end
data/proxo.gemspec ADDED
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/proxo/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "proxo"
7
+ spec.version = Proxo::VERSION
8
+ spec.authors = ["Keegan Leitz"]
9
+ spec.email = ["kjleitz@gmail.com"]
10
+
11
+ spec.summary = "Proxy one port to another port, and intercept/transform/log inbound and outbound TCP messages."
12
+ spec.description = "Proxy one port to another port, and intercept/transform/log inbound and outbound TCP messages."
13
+ spec.homepage = "https://github.com/kjleitz/proxo"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.6.0")
16
+
17
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
18
+
19
+ spec.metadata["homepage_uri"] = spec.homepage
20
+ spec.metadata["source_code_uri"] = "https://github.com/kjleitz/proxo"
21
+ spec.metadata["documentation_uri"] = "https://www.rubydoc.info/gems/proxo/#{Proxo::VERSION}"
22
+
23
+ # Specify which files should be added to the gem when it is released.
24
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
25
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
26
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
27
+ end
28
+ spec.bindir = "exe"
29
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
30
+ spec.require_paths = ["lib"]
31
+
32
+ # Uncomment to register a new dependency of your gem
33
+ # spec.add_dependency "example-gem", "~> 1.0"
34
+
35
+ # For more information and examples about making a new gem, checkout our
36
+ # guide at: https://bundler.io/guides/creating_gem.html
37
+ end
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: proxo
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Keegan Leitz
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-07-29 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Proxy one port to another port, and intercept/transform/log inbound and
14
+ outbound TCP messages.
15
+ email:
16
+ - kjleitz@gmail.com
17
+ executables:
18
+ - proxo
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - ".github/workflows/main.yml"
23
+ - ".gitignore"
24
+ - ".rspec"
25
+ - ".rubocop.yml"
26
+ - CHANGELOG.md
27
+ - Gemfile
28
+ - LICENSE.txt
29
+ - README.md
30
+ - Rakefile
31
+ - bin/console
32
+ - bin/setup
33
+ - exe/proxo
34
+ - lib/proxo.rb
35
+ - lib/proxo/version.rb
36
+ - proxo.gemspec
37
+ homepage: https://github.com/kjleitz/proxo
38
+ licenses:
39
+ - MIT
40
+ metadata:
41
+ allowed_push_host: https://rubygems.org
42
+ homepage_uri: https://github.com/kjleitz/proxo
43
+ source_code_uri: https://github.com/kjleitz/proxo
44
+ documentation_uri: https://www.rubydoc.info/gems/proxo/0.1.0
45
+ post_install_message:
46
+ rdoc_options: []
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: 2.6.0
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ requirements: []
60
+ rubygems_version: 3.0.9
61
+ signing_key:
62
+ specification_version: 4
63
+ summary: Proxy one port to another port, and intercept/transform/log inbound and outbound
64
+ TCP messages.
65
+ test_files: []