proxi 0.1 → 1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|