11package robaho .net .httpserver .extras ;
22
33import java .io .IOException ;
4+ import java .io .InputStream ;
5+ import java .io .OutputStream ;
46import java .net .InetSocketAddress ;
57import java .net .Proxy ;
68import java .net .ProxySelector ;
9+ import java .net .Socket ;
710import java .net .SocketAddress ;
811import java .net .URI ;
912import java .net .URISyntaxException ;
1215import java .net .http .HttpResponse ;
1316import java .util .ArrayList ;
1417import java .util .List ;
18+ import java .util .Objects ;
1519import java .util .Optional ;
1620import java .util .Set ;
1721import java .util .concurrent .ConcurrentHashMap ;
1822import java .util .concurrent .ConcurrentMap ;
23+ import java .util .logging .Logger ;
24+
25+ import javax .net .SocketFactory ;
1926
2027import com .sun .net .httpserver .Headers ;
2128import com .sun .net .httpserver .HttpExchange ;
@@ -31,6 +38,8 @@ public record HostPort(String server,int port,String scheme){}
3138 private final HttpClient proxyClient ;
3239 private final Optional <HostPort > defaultProxy ;
3340
41+ protected final Logger logger = Logger .getLogger ("robaho.net.httpserver.ProxyHandler" );
42+
3443 public ProxyHandler () {
3544 this (Optional .empty ());
3645 }
@@ -55,6 +64,36 @@ public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
5564
5665 @ Override
5766 public void handle (HttpExchange exchange ) throws IOException {
67+ if (exchange .getRequestMethod ().equals ("CONNECT" )) {
68+ if (!authorizeConnect (exchange )) return ;
69+ try (Socket s = SocketFactory .getDefault ().createSocket ()) {
70+ var uri = exchange .getRequestURI ();
71+ var addr = new InetSocketAddress (uri .getScheme (),Integer .parseInt (uri .getSchemeSpecificPart ()));
72+ try {
73+ s .connect (addr );
74+ } catch (Exception e ) {
75+ logger .warning ("failed to connect to " +addr );
76+ exchange .sendResponseHeaders (500 ,-1 );
77+ return ;
78+ }
79+ logger .fine ("connected to " +s .getRemoteSocketAddress ());
80+ exchange .sendResponseHeaders (200 ,0 );
81+
82+ try {
83+ exchange .getHttpContext ().getServer ().getExecutor ().execute (() -> {
84+ try {
85+ transfer (s .getInputStream (),exchange .getResponseBody ());
86+ } catch (IOException ex ) {
87+ ex .printStackTrace ();
88+ }
89+ });
90+ transfer (exchange .getRequestBody (),s .getOutputStream ());
91+ } finally {
92+ logger .fine ("proxy connection to " +s .getRemoteSocketAddress ()+" ended" );
93+ return ;
94+ }
95+ }
96+ }
5897 var proxy = proxyTo (exchange ).orElseThrow (() -> new IOException ("proxy not configured for " +exchange .getRequestURI ()));
5998 var uri = exchange .getRequestURI ();
6099 if (uri .getScheme ()==null ) {
@@ -70,13 +109,43 @@ public void handle(HttpExchange exchange) throws IOException {
70109 exchange .getResponseHeaders ().putAll (response .headers ().map ());
71110 exchange .sendResponseHeaders (response .statusCode (),0 );
72111 try (var os = exchange .getResponseBody ()) {
73- response .body (). transferTo ( os );
112+ transfer ( response .body (), os );
74113 }
75114 } catch (InterruptedException ex ) {
76115 throw new IOException ("unable to proxy request to " +exchange .getRequestURI (),ex );
77116 }
78117 }
79118
119+ /**
120+ * override to check authorization headers. if returning false,
121+ * the implementation must call exchange.sendResponseHeaders() with the appropriate code.
122+ *
123+ * @return true if the CONNECT should proceed, else false
124+ */
125+ protected boolean authorizeConnect (HttpExchange exchange ) {
126+ return true ;
127+ }
128+
129+ private static int DEFAULT_BUFFER_SIZE = 16384 ;
130+ private static long transfer (InputStream in , OutputStream out ) throws IOException {
131+ Objects .requireNonNull (out , "out" );
132+ long transferred = 0 ;
133+ byte [] buffer = new byte [DEFAULT_BUFFER_SIZE ];
134+ int read ;
135+ while ((read = in .read (buffer , 0 , DEFAULT_BUFFER_SIZE )) >= 0 ) {
136+ out .write (buffer , 0 , read );
137+ out .flush ();
138+ if (transferred < Long .MAX_VALUE ) {
139+ try {
140+ transferred = Math .addExact (transferred , read );
141+ } catch (ArithmeticException ignore ) {
142+ transferred = Long .MAX_VALUE ;
143+ }
144+ }
145+ }
146+ return transferred ;
147+ }
148+
80149 private static final Set <String > restrictedHeaders = Set .of ("CONNECTION" ,"HOST" ,"UPGRADE" ,"CONTENT-LENGTH" );
81150
82151 private static String [] headers (Headers headers ) {
0 commit comments