proxi 0.1 → 1.0
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/.gitignore +4 -1
- data/README.md +1 -0
- data/generate_docs +7 -0
- data/lib/proxi.rb +75 -2
- data/lib/proxi/connection.rb +34 -69
- data/lib/proxi/listeners.rb +50 -0
- data/lib/proxi/reporting.rb +57 -0
- data/lib/proxi/server.rb +52 -12
- data/lib/proxi/socket_factory.rb +92 -0
- data/proxi.gemspec +1 -1
- data/proxy.rb +7 -0
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 32e73fc0b66c9d84b526d7b819498c971141804b
|
4
|
+
data.tar.gz: 2708a278216649879f072473ebd6e16375ad0d5e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f7ed6e53d8984ef3127443daf857d68aedc5a70f05c8548be9d3d7fdb58504c84ae37f4d73b9934e72aa3dd8d3cf79ebb8d2ad3f2e93abfeb2f3a63a2aef93e3
|
7
|
+
data.tar.gz: 0bf15ffa0343656882bf45df6c69e7b66fcf346d72db149c60ed0669e132a5bdd88bdf4a6958e1d6281ce4b8d10094d43485e27bec6f415bcf41206021b44809
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -6,6 +6,7 @@ This is intended as a development tool, for when you want to see exactly what
|
|
6
6
|
goes over the wire, want to log or save specific requests, responses, simulate
|
7
7
|
timeouts or slow connections, etc.
|
8
8
|
|
9
|
+
For documentation see the [annotated source code](http://plexus.github.io/proxi/).
|
9
10
|
|
10
11
|
## License
|
11
12
|
|
data/generate_docs
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
cat lib/proxi.rb lib/proxi/server.rb lib/proxi/connection.rb lib/proxi/socket_factory.rb lib/proxi/reporting.rb lib/proxi/listeners.rb > doc/proxi
|
2
|
+
|
3
|
+
# Use https://github.com/Billiam/rocco , seems to at least mostly still work
|
4
|
+
|
5
|
+
RUBYOPT=-I~/github/rocco/lib ~/github/rocco/bin/rocco --docblocks doc/proxi
|
6
|
+
|
7
|
+
firefox-dev doc/proxi.html
|
data/lib/proxi.rb
CHANGED
@@ -1,13 +1,86 @@
|
|
1
1
|
require 'socket'
|
2
2
|
require 'thread'
|
3
|
-
require 'optparse'
|
4
|
-
require 'tmpdir'
|
5
3
|
require 'openssl'
|
6
4
|
|
7
5
|
require 'wisper'
|
8
6
|
|
7
|
+
# [Proxi](https://github.com/plexus/proxi) gives you flexible TCP/HTTP proxy
|
8
|
+
# servers for use during development.
|
9
|
+
#
|
10
|
+
# This can be useful when developing against external services, to see what goes
|
11
|
+
# over the wire, capture responses, or simulate timeouts.
|
12
|
+
#
|
13
|
+
# There are three main concepts in Proxi, `Server`, `Connection`, and `Socket`.
|
14
|
+
# A server listens locally for incoming requests. When it receives a request, it
|
15
|
+
# establishes a `Connection`, which opens a socket to the remote host, and then
|
16
|
+
# acts as a bidirectional pipe between the incoming and outgoing network
|
17
|
+
# sockets.
|
18
|
+
#
|
19
|
+
# To allow for multiple ways to handle the proxying, and multiple strategies for
|
20
|
+
# making remote connections, the `Server` does not create `Connections`
|
21
|
+
# directly, but instead delegates this to a "connection factory".
|
22
|
+
#
|
23
|
+
# A `Connection` in turn delegates how to open a remote socket to a "socket
|
24
|
+
# factory".
|
25
|
+
#
|
26
|
+
# Both Servers and Connections are observable, they emit events that objects can
|
27
|
+
# subscribe to.
|
28
|
+
#
|
29
|
+
# To use Proxi, hook up these factories, and register event listeners, and then
|
30
|
+
# start the server.
|
9
31
|
module Proxi
|
32
|
+
|
33
|
+
# ## Basic examples
|
34
|
+
#
|
35
|
+
# These are provided for basic use cases, and as a starting point for more
|
36
|
+
# complex uses. They return the server instance, call `#call` to start the
|
37
|
+
# server, or use `on` or `subscribe` to listen to events.
|
38
|
+
|
39
|
+
# With `Proxy.tcp_proxy` you get basic proxying from a local port to a remote
|
40
|
+
# host and port, all bytes are simply forwarded without caring about their
|
41
|
+
# contents.
|
42
|
+
#
|
43
|
+
# For example:
|
44
|
+
#
|
45
|
+
# Proxi.tcp_proxy(3000, 'foo.example.com', 4000).call
|
46
|
+
def self.tcp_proxy(local_port, remote_host, remote_port)
|
47
|
+
reporter = ConsoleReporter.new
|
48
|
+
|
49
|
+
connection_factory = -> in_socket do
|
50
|
+
socket_factory = TCPSocketFactory.new(remote_host, remote_port)
|
51
|
+
Connection.new(in_socket, socket_factory).subscribe(reporter)
|
52
|
+
end
|
53
|
+
|
54
|
+
Server.new(local_port, connection_factory)
|
55
|
+
end
|
56
|
+
|
57
|
+
# `Proxi.http_host_proxy` allows proxying to multiple remote hosts, based on
|
58
|
+
# the HTTP `Host:` header. To use it, gather the IP addresses that correspond
|
59
|
+
# to each domain name, and provide this name-to-ip mapping to
|
60
|
+
# `http_host_proxy`. Now configure these domain names in `/etc/hosts` to point
|
61
|
+
# to the local host, so Proxi can intercept traffic intended for these
|
62
|
+
# domains.
|
63
|
+
#
|
64
|
+
# For example
|
65
|
+
#
|
66
|
+
# Proxi.http_host_proxy(80, {'foo.example.org' => '10.10.0.1'}).call
|
67
|
+
def self.http_host_proxy(local_port, host_mapping)
|
68
|
+
reporter = ConsoleReporter.new
|
69
|
+
|
70
|
+
connection_factory = -> in_socket do
|
71
|
+
socket_factory = HTTPHostSocketFactory.new(host_mapping)
|
72
|
+
Connection
|
73
|
+
.new(in_socket, socket_factory)
|
74
|
+
.subscribe(socket_factory, on: :data_in)
|
75
|
+
.subscribe(reporter)
|
76
|
+
end
|
77
|
+
|
78
|
+
Server.new(local_port, connection_factory)
|
79
|
+
end
|
10
80
|
end
|
11
81
|
|
12
82
|
require_relative 'proxi/server'
|
13
83
|
require_relative 'proxi/connection'
|
84
|
+
require_relative 'proxi/socket_factory'
|
85
|
+
require_relative 'proxi/reporting'
|
86
|
+
require_relative 'proxi/listeners'
|
data/lib/proxi/connection.rb
CHANGED
@@ -1,18 +1,45 @@
|
|
1
|
+
# ## Proxi::Connection
|
2
|
+
#
|
1
3
|
module Proxi
|
4
|
+
|
5
|
+
# A `Connection` is a bidirectional pipe between two sockets.
|
6
|
+
#
|
7
|
+
# The proxy server hands it the socket for the incoming request from, and
|
8
|
+
# `Connection` then initiates an outgoing request, after which it forwards all
|
9
|
+
# traffic in both directions.
|
10
|
+
#
|
11
|
+
# Creating the outgoing request is delegated to a `Proxi::SocketFactory`. The
|
12
|
+
# reason being that the type of socket can vary (`TCPSocket`, `SSLSocket`), or
|
13
|
+
# there might be some logic involved to dispatch to the correct host, e.g.
|
14
|
+
# based on the HTTP Host header (cfr. `Proxi::HTTPHostSocketFactory`).
|
15
|
+
#
|
16
|
+
# A socket factory can subscribe to events to make informed decision, e.g. to
|
17
|
+
# inspect incoming data for HTTP headers.
|
18
|
+
#
|
19
|
+
# Proxi::Connection broadcasts the following events:
|
20
|
+
#
|
21
|
+
# - `start_connection(Proxi::Connection)`
|
22
|
+
# - `end_connection(Proxi::Connection)`
|
23
|
+
# - `main_loop_error(Proxi::Connection, Exception)`
|
24
|
+
# - `data_in(Proxi::Connection, Array)`
|
25
|
+
# - `data_out(Proxi::Connection, Array)`
|
2
26
|
class Connection
|
3
27
|
include Wisper::Publisher
|
4
28
|
|
5
|
-
attr_reader :in_socket, :thread
|
29
|
+
attr_reader :in_socket, :thread
|
6
30
|
|
7
|
-
def initialize(in_socket,
|
31
|
+
def initialize(in_socket, socket_factory, max_block_size: 4096)
|
8
32
|
@in_socket = in_socket
|
9
|
-
@
|
10
|
-
@
|
33
|
+
@socket_factory = socket_factory
|
34
|
+
@max_block_size = max_block_size
|
11
35
|
end
|
12
36
|
|
37
|
+
# `Connection#call` starts the connection handler thread. This is called by
|
38
|
+
# the server, and spawns a new Thread that handles the forwarding of data.
|
13
39
|
def call
|
14
40
|
broadcast(:start_connection, self)
|
15
41
|
@thread = Thread.new { proxy_loop }
|
42
|
+
self
|
16
43
|
end
|
17
44
|
|
18
45
|
def alive?
|
@@ -26,11 +53,12 @@ module Proxi
|
|
26
53
|
private
|
27
54
|
|
28
55
|
def out_socket
|
29
|
-
@out_socket ||=
|
56
|
+
@out_socket ||= @socket_factory.call
|
30
57
|
end
|
31
58
|
|
32
59
|
def proxy_loop
|
33
60
|
begin
|
61
|
+
handle_socket(in_socket)
|
34
62
|
loop do
|
35
63
|
begin
|
36
64
|
ready_sockets.each do |socket|
|
@@ -55,7 +83,7 @@ module Proxi
|
|
55
83
|
end
|
56
84
|
|
57
85
|
def handle_socket(socket)
|
58
|
-
data = socket.readpartial(
|
86
|
+
data = socket.readpartial(@max_block_size)
|
59
87
|
|
60
88
|
if socket == in_socket
|
61
89
|
broadcast(:data_in, self, data)
|
@@ -69,67 +97,4 @@ module Proxi
|
|
69
97
|
end
|
70
98
|
end
|
71
99
|
|
72
|
-
module SSLConnection
|
73
|
-
def connect
|
74
|
-
@out_socket = OpenSSL::SSL::SSLSocket.new(super)
|
75
|
-
@out_socket.connect
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
class HTTPConnection < Connection
|
80
|
-
def initialize(in_socket, host_to_ip)
|
81
|
-
@in_socket, @host_to_ip = in_socket, host_to_ip
|
82
|
-
@new = true
|
83
|
-
end
|
84
|
-
|
85
|
-
def handle_socket(socket)
|
86
|
-
data = socket.readpartial(4096)
|
87
|
-
|
88
|
-
if socket == in_socket
|
89
|
-
broadcast(:data_in, self, data)
|
90
|
-
|
91
|
-
if @new
|
92
|
-
@first_packet = data
|
93
|
-
@new = false
|
94
|
-
end
|
95
|
-
|
96
|
-
out_socket.write data
|
97
|
-
out_socket.flush
|
98
|
-
else
|
99
|
-
broadcast(:data_out, self, data)
|
100
|
-
in_socket.write data
|
101
|
-
in_socket.flush
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
def ready_sockets
|
106
|
-
if @new
|
107
|
-
sockets = [in_socket]
|
108
|
-
else
|
109
|
-
sockets = [in_socket, out_socket]
|
110
|
-
end
|
111
|
-
IO.select(sockets).first
|
112
|
-
end
|
113
|
-
|
114
|
-
def out_socket
|
115
|
-
host, port = @host_to_ip.fetch(headers["host"]).split(':')
|
116
|
-
port ||= 80
|
117
|
-
@out_socket ||= TCPSocket.new(host, port.to_i)
|
118
|
-
end
|
119
|
-
|
120
|
-
def headers
|
121
|
-
Hash[
|
122
|
-
@first_packet
|
123
|
-
.sub(/\r\n\r\n.*/m, '')
|
124
|
-
.each_line
|
125
|
-
.drop(1) # GET / HTTP/1.1
|
126
|
-
.map do |line|
|
127
|
-
k,v = line.split(':', 2)
|
128
|
-
[k.downcase, v.strip].tap do |header|
|
129
|
-
broadcast(:header, self, *header)
|
130
|
-
end
|
131
|
-
end
|
132
|
-
]
|
133
|
-
end
|
134
|
-
end
|
135
100
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# ## Server Listeners
|
2
|
+
#
|
3
|
+
# These can be attached to a server to add extra behavior
|
4
|
+
#
|
5
|
+
module Proxi
|
6
|
+
|
7
|
+
# Log all incoming and outgoing traffic to files under `/tmp`
|
8
|
+
#
|
9
|
+
# For example:
|
10
|
+
#
|
11
|
+
# Proxi.tcp_server(...).subscribe(RequestResponseLogging.new).call
|
12
|
+
#
|
13
|
+
# The in and outgoing traffic will be captured per connection in
|
14
|
+
# `/tmp/proxi.1.in` and `/tmp/proxi.1.out`; `/tmp/proxi.2.in`, etc.
|
15
|
+
class RequestResponseLogging
|
16
|
+
def initialize(dir: Dir.tmpdir, name: "proxi")
|
17
|
+
@dir = dir
|
18
|
+
@name = name
|
19
|
+
@count = 0
|
20
|
+
@mutex = Mutex.new
|
21
|
+
end
|
22
|
+
|
23
|
+
def new_connection(connection)
|
24
|
+
count = @mutex.synchronize { @count += 1 }
|
25
|
+
in_fd = File.open(log_name(count, "in"), 'w')
|
26
|
+
out_fd = File.open(log_name(count, "out"), 'w')
|
27
|
+
|
28
|
+
connection
|
29
|
+
.on(:data_in) { |_, data| in_fd.write(data) ; in_fd.flush }
|
30
|
+
.on(:data_out) { |_, data| out_fd.write(data) ; out_fd.flush }
|
31
|
+
.on(:end_connection) { in_fd.close ; out_fd.close }
|
32
|
+
end
|
33
|
+
|
34
|
+
def log_name(num, suffix)
|
35
|
+
'%s/%s.%d.%s' % [@dir, @name, num, suffix]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Wait before handing back data coming from the remote, this simulates a slow
|
40
|
+
# connection, and can be used to test timeouts.
|
41
|
+
class SlowDown
|
42
|
+
def initialize(wait_seconds: 5)
|
43
|
+
@wait_seconds = wait_seconds
|
44
|
+
end
|
45
|
+
|
46
|
+
def new_connection(connection)
|
47
|
+
connection.on(:data_out) { sleep @wait_seconds }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# ### Reporting
|
2
|
+
#
|
3
|
+
# Proxi's server and connection classes don't have any logging or UI
|
4
|
+
# capabilities built in, but they broadcast events that we can listen to to perform these tasks.
|
5
|
+
|
6
|
+
module Proxi
|
7
|
+
# This is a very basic console reporter to see what's happening
|
8
|
+
#
|
9
|
+
# Subscribe to connection events, and you will see output that looks like this
|
10
|
+
#
|
11
|
+
# 1. +++
|
12
|
+
# 1. < 91
|
13
|
+
# 2. +++
|
14
|
+
# 3. +++
|
15
|
+
# 2. < 91
|
16
|
+
# 3. < 91
|
17
|
+
# 1. > 4096
|
18
|
+
# 1. > 3422
|
19
|
+
# 1. ---
|
20
|
+
#
|
21
|
+
# Each connection gets a unique incremental number assigned, followed by:
|
22
|
+
#
|
23
|
+
# - '+++' new connection
|
24
|
+
# - '---' connection closed
|
25
|
+
# - '< 1234' number of bytes proxied to the remote
|
26
|
+
# - '> 1234' number of bytes proxied back from the remote
|
27
|
+
class ConsoleReporter
|
28
|
+
def initialize
|
29
|
+
@count = 0
|
30
|
+
@mutex = Mutex.new
|
31
|
+
@connections = {}
|
32
|
+
end
|
33
|
+
|
34
|
+
def start_connection(conn)
|
35
|
+
@mutex.synchronize { @connections[conn] = (@count += 1) }
|
36
|
+
puts "#{@connections[conn]}. +++"
|
37
|
+
end
|
38
|
+
|
39
|
+
def end_connection(conn)
|
40
|
+
puts "#{@connections[conn]}. ---"
|
41
|
+
@connections.delete(conn)
|
42
|
+
end
|
43
|
+
|
44
|
+
def data_in(conn, data)
|
45
|
+
puts "#{@connections[conn]}. < #{data.size}"
|
46
|
+
end
|
47
|
+
|
48
|
+
def data_out(conn, data)
|
49
|
+
puts "#{@connections[conn]}. > #{data.size}"
|
50
|
+
end
|
51
|
+
|
52
|
+
def main_loop_error(conn, exc)
|
53
|
+
STDERR.puts "#{@connections[conn]}. #{exc.class} #{exc.message}"
|
54
|
+
STDERR.puts exc.backtrace
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/proxi/server.rb
CHANGED
@@ -1,19 +1,45 @@
|
|
1
|
+
# ## Proxi::Server
|
2
|
+
#
|
1
3
|
module Proxi
|
4
|
+
|
5
|
+
# `Proxi::Server` accepts TCP requests, and forwards them, by creating an
|
6
|
+
# outbound connection and forwarding traffic in both directions.
|
7
|
+
#
|
8
|
+
# The destination of the outbound connection, and the forwarding of data, is
|
9
|
+
# handled by a `Proxi::Connection`, created by a factory object, which can be a
|
10
|
+
# lambda.
|
11
|
+
#
|
12
|
+
# Start listening for connections by calling #call.
|
13
|
+
#
|
14
|
+
# `Proxi::Server` broadcasts the following events:
|
15
|
+
#
|
16
|
+
# - `new_connection(Proxi::Connection)`
|
17
|
+
# - `dead_connection(Proxi::Connection)`
|
2
18
|
class Server
|
3
19
|
include Wisper::Publisher
|
4
20
|
|
5
|
-
|
6
|
-
|
7
|
-
|
21
|
+
# Public: Initialize a Server
|
22
|
+
#
|
23
|
+
# listen_port - The String or Integer of the port to listen to for
|
24
|
+
# incoming connections
|
25
|
+
# connection_factory - Implements #call(in_socket) and returns a
|
26
|
+
# Proxi::Connection
|
27
|
+
# max_connections - The maximum amount of parallel connections to handle
|
28
|
+
# at once
|
29
|
+
def initialize(listen_port, connection_factory, max_connections: 5)
|
30
|
+
@listen_port = listen_port
|
8
31
|
@connection_factory = connection_factory
|
9
|
-
|
10
|
-
@server = TCPServer.new(nil, listen_port)
|
11
|
-
|
32
|
+
@max_connections = 5
|
12
33
|
@connections = []
|
13
34
|
end
|
14
35
|
|
36
|
+
# Public: Start the server
|
37
|
+
#
|
38
|
+
# Start accepting and forwarding requests
|
15
39
|
def call
|
16
|
-
|
40
|
+
@server = TCPServer.new('localhost', @listen_port)
|
41
|
+
|
42
|
+
until @server.closed?
|
17
43
|
in_socket = @server.accept
|
18
44
|
connection = @connection_factory.call(in_socket)
|
19
45
|
|
@@ -21,22 +47,36 @@ module Proxi
|
|
21
47
|
|
22
48
|
@connections.push(connection)
|
23
49
|
|
24
|
-
connection.call
|
50
|
+
connection.call # spawns a new thread that handles proxying
|
25
51
|
|
26
52
|
reap_connections
|
27
|
-
while @connections.size >=
|
53
|
+
while @connections.size >= @max_connections
|
28
54
|
sleep 1
|
29
55
|
reap_connections
|
30
56
|
end
|
31
57
|
end
|
58
|
+
ensure
|
59
|
+
close unless @server.closed?
|
60
|
+
end
|
61
|
+
|
62
|
+
# Public: close the TCP server socket
|
63
|
+
#
|
64
|
+
# Included for completeness, note that if the proxy server is active it will
|
65
|
+
# likely be blocking on TCPServer#accept, and the server port will stay open
|
66
|
+
# until it has accepted one final request.
|
67
|
+
def close
|
68
|
+
@server.close
|
32
69
|
end
|
33
70
|
|
71
|
+
private
|
72
|
+
|
34
73
|
def reap_connections
|
35
|
-
@connections = @connections.select do |
|
36
|
-
if
|
74
|
+
@connections = @connections.select do |conn|
|
75
|
+
if conn.alive?
|
37
76
|
true
|
38
77
|
else
|
39
|
-
|
78
|
+
broadcast(:dead_connection, conn)
|
79
|
+
conn.join_thread
|
40
80
|
false
|
41
81
|
end
|
42
82
|
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# ## Socket factories
|
2
|
+
#
|
3
|
+
module Proxi
|
4
|
+
# ### TCPSocketFactory
|
5
|
+
#
|
6
|
+
# This is the most vanilla type of socket factory.
|
7
|
+
#
|
8
|
+
# Suitable when all requests need to be forwarded to the same host and port.
|
9
|
+
class TCPSocketFactory
|
10
|
+
def initialize(remote_host, remote_port)
|
11
|
+
@remote_host, @remote_port = remote_host, remote_port
|
12
|
+
end
|
13
|
+
|
14
|
+
def call
|
15
|
+
TCPSocket.new(@remote_host, @remote_port)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# ### SSLSocketFactory
|
20
|
+
#
|
21
|
+
# This will set up an encrypted (SSL, https) connection to the target host.
|
22
|
+
# This way the proxy server communicates *unencrypted* locally, but
|
23
|
+
# encrypts/decrypts communication with the remote host.
|
24
|
+
#
|
25
|
+
# If you want to forward SSL connections as-is, use a `TCPSocketFactory`, in
|
26
|
+
# that case however you won't be able to inspect any data passing through,
|
27
|
+
# since it will be encrypted.
|
28
|
+
class SSLSocketFactory < TCPSocketFactory
|
29
|
+
def call
|
30
|
+
OpenSSL::SSL::SSLSocket.new(super).tap(&:connect)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# ### HTTPHostSocketFactory
|
35
|
+
#
|
36
|
+
# Dispatches HTTP traffic to multiple hosts, based on the HTTP `Host:` header.
|
37
|
+
#
|
38
|
+
# HTTPHostSocketFactory expects to receive data events from the connection, so
|
39
|
+
# make sure you subscribe it to connection events. (see `Proxi.http_proxy` for
|
40
|
+
# an example).
|
41
|
+
#
|
42
|
+
# To use this effectively, configure your local `/etc/hosts` so the relevant
|
43
|
+
# domains point to localhost. That way the proxy will be able to intercept
|
44
|
+
# them.
|
45
|
+
#
|
46
|
+
# This class is single use only! Create a new instance for each `Proxi::Connection`.
|
47
|
+
class HTTPHostSocketFactory
|
48
|
+
|
49
|
+
# Initialize a HTTPHostSocketFactory
|
50
|
+
#
|
51
|
+
# `host_mapping` - A Hash mapping hostnames to IP addresses, and, optionally, ports
|
52
|
+
#
|
53
|
+
# For example:
|
54
|
+
#
|
55
|
+
# HTTPHostSocketFactory.new(
|
56
|
+
# 'foo.example.com' => '10.10.10.1:8080',
|
57
|
+
# 'bar.example.com' => '10.10.10.2:8080'
|
58
|
+
# )
|
59
|
+
def initialize(host_mapping)
|
60
|
+
@host_mapping = host_mapping
|
61
|
+
end
|
62
|
+
|
63
|
+
# This is an event listener, it will be broadcast by the `Connection` whenever
|
64
|
+
# it gets new request data. We capture the first packet, assuming it
|
65
|
+
# contains the HTTP headers.
|
66
|
+
#
|
67
|
+
# `Connection` will only request an outgoing socket from us (call `#call`)
|
68
|
+
# after it received the initial request payload.
|
69
|
+
def data_in(connection, data)
|
70
|
+
@first_packet ||= data
|
71
|
+
end
|
72
|
+
|
73
|
+
def call
|
74
|
+
host, port = @host_to_ip.fetch(headers["host"]).split(':')
|
75
|
+
port ||= 80
|
76
|
+
TCPSocket.new(host, port.to_i)
|
77
|
+
end
|
78
|
+
|
79
|
+
def headers
|
80
|
+
Hash[
|
81
|
+
@first_packet
|
82
|
+
.sub(/\r\n\r\n.*/m, '')
|
83
|
+
.each_line
|
84
|
+
.drop(1) # GET / HTTP/1.1
|
85
|
+
.map do |line|
|
86
|
+
k,v = line.split(':', 2)
|
87
|
+
[k.downcase, v.strip]
|
88
|
+
end
|
89
|
+
]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
data/proxi.gemspec
CHANGED
data/proxy.rb
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
#!/usr/bin/ruby
|
2
2
|
|
3
|
+
# This is my original proxy.rb script that served me well for many years, before
|
4
|
+
# I decided to clean it up and turn it into a gem.
|
5
|
+
#
|
6
|
+
# Have a look under lib/ for the much improved version. Leaving it here just a
|
7
|
+
# little longer for reference. There are still a few features that haven't been
|
8
|
+
# ported yet.
|
9
|
+
|
3
10
|
require 'socket'
|
4
11
|
require 'thread'
|
5
12
|
require 'optparse'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: proxi
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0
|
4
|
+
version: '1.0'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Arne Brasseur
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-10-
|
11
|
+
date: 2015-10-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: wisper
|
@@ -51,9 +51,13 @@ files:
|
|
51
51
|
- Gemfile.lock
|
52
52
|
- LICENSE
|
53
53
|
- README.md
|
54
|
+
- generate_docs
|
54
55
|
- lib/proxi.rb
|
55
56
|
- lib/proxi/connection.rb
|
57
|
+
- lib/proxi/listeners.rb
|
58
|
+
- lib/proxi/reporting.rb
|
56
59
|
- lib/proxi/server.rb
|
60
|
+
- lib/proxi/socket_factory.rb
|
57
61
|
- proxi.gemspec
|
58
62
|
- proxy.rb
|
59
63
|
homepage: https://github.com/plexus/proxi
|