Not logged in.  Login/Logout/Register | List snippets | | Create snippet | Upload image | Upload data

273
LINES

< > BotCompany Repo | #1032715 // StandaloneHttpProxy [OK]

JavaX fragment (include) [tags: use-pretranspiled]

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  
}

Author comment

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]