Libraryless. Click here for Pure Java version (11966L/67K).
1 | sclass StandaloneHttpProxy implements AutoCloseable { |
2 | bool debug; |
3 | bool serveLocalhostOnly; // IMPORTANT if you need it! |
4 | bool sendXProying = true; |
5 | int port = 8443; |
6 | bool rewriteHostHeader = true; // Send a modified Host header |
7 | int upBufSize = 1; // otherwise POST seems to break... |
8 | int downBufSize = 1024; |
9 | |
10 | Set<S> blockedIPs = syncSet(); |
11 | |
12 | // set or override this! |
13 | swappable HostAndPort forwardServerAndPort(Request req) { null; } |
14 | |
15 | // can set or override this for https |
16 | swappable ServerSocket makeServerSocket() { |
17 | ctex { ret new ServerSocket(port); } |
18 | } |
19 | |
20 | // internal |
21 | Thread acceptThread; |
22 | ServerSocket serverSocket; |
23 | |
24 | void start() throws IOException { |
25 | serverSocket = makeServerSocket(); |
26 | |
27 | acceptThread = startThread("HTTP Proxy Accept Port " + port, r acceptLoop); |
28 | print("HTTP proxy on port " + port + " started."); |
29 | } |
30 | |
31 | void acceptLoop() throws IOException { |
32 | while (true) { |
33 | try { |
34 | Socket s = serverSocket.accept(); |
35 | new Request(s).run(); |
36 | } catch (SocketTimeoutException e) { |
37 | } |
38 | } |
39 | } |
40 | |
41 | swappable S rewriteHeaderLine(S line) { ret line; } |
42 | |
43 | svoid headerSend(OutputStream out, S header) ctex { |
44 | print("Sending header: " + header); |
45 | out.write((header + "\r\n").getBytes("US-ASCII")); |
46 | } |
47 | |
48 | close { dispose serverSocket; } |
49 | |
50 | class Request is AutoCloseable { |
51 | Socket s; // client socket |
52 | S client; |
53 | bool blocked; |
54 | Socket outSocket; |
55 | |
56 | // TODO: Values may be inaccurate when there is stream rewriting |
57 | // by overriding outProxied and/or outOutProxied. |
58 | new AtomicLong transmitted; |
59 | new AtomicLong outTransmitted; |
60 | |
61 | S threadName = "StandaloneHttpProxy"; |
62 | Thread thread, inThread, outThread; |
63 | OutputStream outDirect, outOutDirect; |
64 | OutputStream outProxied, outOutProxied; |
65 | InputStream in, outIn; |
66 | MultiMap<S> incomingHeaders = ciMultiMap(); |
67 | bool forwardedFor; |
68 | S host; |
69 | new ByteArrayOutputStream headersOut; |
70 | bool headersRead; |
71 | new CloseablesHolder resources; |
72 | |
73 | *(Socket *s) {} |
74 | *(Socket *s, InputStream *in, OutputStream out) { setOut(out); } |
75 | |
76 | swappable void setOut(OutputStream out) { |
77 | outDirect = outProxied = out; |
78 | } |
79 | |
80 | swappable void setOutOut(OutputStream outOut) { |
81 | outOutDirect = outOutProxied = outOut; |
82 | } |
83 | |
84 | run { |
85 | try { |
86 | client = dropPrefix("/", s.getInetAddress().toString()); // Hmm... how to get the actual IP properly? |
87 | |
88 | if (serveLocalhostOnly && !socketIsLocalhost(s)) { |
89 | print("Dropping non-localhost connection from " + client); |
90 | ret with close(); |
91 | } |
92 | |
93 | threadName = "PROXY: Handling client " + client; |
94 | |
95 | if (blocked = blockedIPs.contains(client)) { |
96 | print("Blocked IP!! " + client); |
97 | ret with close(); |
98 | } |
99 | |
100 | run2(); |
101 | } on fail e { |
102 | print("Closing because of: " + e); |
103 | close(); |
104 | } |
105 | } // Request.run |
106 | |
107 | void run2 { |
108 | try { |
109 | thread = startDaemonThread(threadName, r handleIt_impl); |
110 | } on fail e { |
111 | print("Closing because of: " + e); |
112 | close(); |
113 | } |
114 | } |
115 | |
116 | void handleIt_impl() throws IOException { |
117 | try { |
118 | print("HTTP Proxy Incoming!"); |
119 | |
120 | if (outDirect == null) setOut(s.getOutputStream()); |
121 | if (in == null) in = s.getInputStream(); |
122 | |
123 | // collect headers |
124 | |
125 | if (!headersRead) while (true) { |
126 | S line = readHttpHeaderLine(in); |
127 | if (line == null) ret; |
128 | print("Header line read: " + quote(line)); |
129 | |
130 | int i = indexOf(line, ':'); |
131 | if (i >= 0) |
132 | incomingHeaders.put(trim(takeFirst(i, line)), trim(dropFirst(i+1, line))); |
133 | |
134 | bool isHostLine = swic(line, "Host:"); |
135 | if (isHostLine) |
136 | host = dropPrefixIgnoreCase("Host:", line).trim(); |
137 | |
138 | if (!isHostLine || !rewriteHostHeader) { |
139 | S rewritten; |
140 | if (startsWithIgnoreCase(line, "X-Forwarded-For:")) { |
141 | forwardedFor = true; |
142 | rewritten = line + ", " + client; |
143 | } else |
144 | rewritten = empty(line) ? line : rewriteHeaderLine(line); |
145 | if (!eq(rewritten, line)) |
146 | print("Rewritten as: " + quote(rewritten)); |
147 | if (nempty(rewritten)) { |
148 | byte[] bytes = (rewritten + "\r\n").getBytes("US-ASCII"); |
149 | //print("Sending: " + bytesToHex(bytes)); |
150 | headersOut.write(bytes); |
151 | } |
152 | } |
153 | |
154 | if (l(line) == 0) |
155 | break; |
156 | } |
157 | |
158 | HostAndPort hap = forwardServerAndPort(this); |
159 | if (hap == null || empty(hap.host) || hap.port == 0) |
160 | ret with print("Can't forward request"); |
161 | |
162 | S forwardServer = hap.host; |
163 | int port = hap.port; |
164 | |
165 | outSocket = new Socket(forwardServer, port); |
166 | print("Connected to " + forwardServer + ":" + port); |
167 | outOutDirect = outOutProxied = outSocket.getOutputStream(); |
168 | outIn = outSocket.getInputStream(); |
169 | |
170 | byte[] headerBytes = headersOut.toByteArray(); |
171 | if (containsLine(fromUtf8(headerBytes), "X-Proxying: yes")) |
172 | ret with print("Won't double-proxy"); |
173 | |
174 | outOutProxied.write(headerBytes); |
175 | |
176 | if (!forwardedFor) |
177 | headerSend(outOutProxied, "X-Forwarded-For: " + client); |
178 | |
179 | if (rewriteHostHeader) |
180 | headerSend(outOutProxied, "Host: " + addPortToHost_drop80(host, port)); |
181 | |
182 | if (sendXProying) |
183 | headerSend(outOutProxied, "X-Proxying: yes"); |
184 | |
185 | headerSend(outOutProxied, "X-OriginalPort: " + s.getLocalPort()); |
186 | |
187 | headerSend(outOutProxied, ""); // end of headers |
188 | |
189 | outOutProxied.flush(); |
190 | |
191 | // forward content in both directions |
192 | |
193 | inThread = startThread("Proxy In", r proxyIn_impl); |
194 | outThread = startThread("Proxy Out", r proxyOut_impl); |
195 | |
196 | joinThreads(inThread, outThread); |
197 | } catch (Exception e) { |
198 | if (isIOException(e)) |
199 | print("[internal] " + e); |
200 | else |
201 | print("stack exit: " + renderStackTrace(e)); |
202 | } finally { |
203 | print("Request done"); |
204 | close(); |
205 | } |
206 | } // end of handleIt_impl |
207 | |
208 | void proxyIn_impl() throws IOException { |
209 | try { |
210 | byte[] buf = new[downBufSize]; |
211 | while (true) { |
212 | int n = outIn.read(buf); |
213 | if (n <= 0) ret with print("proxyIn: n=" + n); |
214 | outProxied.write(buf, 0, n); |
215 | long t = transmitted.addAndGet(n); |
216 | if (t % 100000 == 0) print("Transmitted to client: " + t); |
217 | } |
218 | } finally { |
219 | print("Server closed stream, shutting down client output."); |
220 | s.shutdownOutput(); |
221 | } |
222 | } |
223 | |
224 | void proxyOut_impl() throws IOException { |
225 | try { |
226 | print("Proxy Out started."); |
227 | byte[] buf = new[upBufSize]; |
228 | while (true) { |
229 | int n = in.read(buf); |
230 | if (n <= 0) ret with print("proxyOut: n=" + n); |
231 | outOutProxied.write(buf, 0, n); |
232 | if (outTransmitted.get() == 0) |
233 | print("Proxy Out first byte!"); |
234 | long t = outTransmitted.addAndGet(n); |
235 | if (t % (debug ? 10 : 100000) == 0) print("Transmitted to server: " + t); |
236 | } |
237 | } finally { |
238 | print("Client closed stream, shutting down server output."); |
239 | outSocket.shutdownOutput(); |
240 | } |
241 | } |
242 | |
243 | close { |
244 | if (s == null && outSocket == null) ret; |
245 | |
246 | print("Proxy request done, got " + transmitted! + ", sent " + outTransmitted! + " + headers"); |
247 | dispose s; |
248 | dispose outSocket; |
249 | dispose resources; |
250 | } |
251 | } // end of Request |
252 | |
253 | Request takeOverIncomingSocket(S host, Socket socket, InputStream in, OutputStream out, byte[] headers) throws IOException { |
254 | ret takeOverIncomingSocket(host, socket, in, out, null); |
255 | } |
256 | |
257 | // rewriteHostHeader should be false when you use this method |
258 | Request takeOverIncomingSocket(S host, Socket socket, InputStream in, OutputStream out, byte[] headers, IVF1<Request> customize) throws IOException { |
259 | Request r = new(socket, in, out); |
260 | r.host = host; |
261 | r.headersOut.write(headers); |
262 | |
263 | // various flags to read no more headers |
264 | // & send just the headers we received |
265 | r.headersRead = true; |
266 | r.forwardedFor = true; |
267 | |
268 | callF(customize, r); |
269 | |
270 | r.run2(); |
271 | ret r; |
272 | } |
273 | } |
Began life as a copy of #1002822
download show line numbers debug dex old transpilations
Travelled to 3 computer(s): bhatertpkbcr, mowyntqkapby, mqqgnosmbjvj
No comments. add comment
Snippet ID: | #1032715 |
Snippet name: | StandaloneHttpProxy [OK] |
Eternal ID of this version: | #1032715/52 |
Text MD5: | ea202c43c33db8e9a78171af87897323 |
Transpilation MD5: | 49cd695426a9957a10a8574a7dad8702 |
Author: | stefan |
Category: | javax / http |
Type: | JavaX fragment (include) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2023-04-17 19:37:13 |
Source code size: | 8788 bytes / 273 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 314 / 628 |
Version history: | 51 change(s) |
Referenced in: | [show references] |