22 * or visit www.oracle.com if you need additional information or have any |
22 * or visit www.oracle.com if you need additional information or have any |
23 * questions. |
23 * questions. |
24 */ |
24 */ |
25 |
25 |
26 import com.sun.net.httpserver.BasicAuthenticator; |
26 import com.sun.net.httpserver.BasicAuthenticator; |
27 import com.sun.net.httpserver.Filter; |
|
28 import com.sun.net.httpserver.Headers; |
|
29 import com.sun.net.httpserver.HttpContext; |
|
30 import com.sun.net.httpserver.HttpExchange; |
|
31 import com.sun.net.httpserver.HttpHandler; |
|
32 import com.sun.net.httpserver.HttpServer; |
27 import com.sun.net.httpserver.HttpServer; |
33 import com.sun.net.httpserver.HttpsConfigurator; |
28 import com.sun.net.httpserver.HttpsConfigurator; |
34 import com.sun.net.httpserver.HttpsParameters; |
29 import com.sun.net.httpserver.HttpsParameters; |
35 import com.sun.net.httpserver.HttpsServer; |
30 import com.sun.net.httpserver.HttpsServer; |
36 import java.io.IOException; |
31 import java.io.IOException; |
40 import java.io.PrintWriter; |
35 import java.io.PrintWriter; |
41 import java.io.Writer; |
36 import java.io.Writer; |
42 import java.math.BigInteger; |
37 import java.math.BigInteger; |
43 import java.net.Authenticator; |
38 import java.net.Authenticator; |
44 import java.net.HttpURLConnection; |
39 import java.net.HttpURLConnection; |
45 import java.net.InetAddress; |
|
46 import java.net.InetSocketAddress; |
40 import java.net.InetSocketAddress; |
47 import java.net.MalformedURLException; |
41 import java.net.MalformedURLException; |
48 import java.net.PasswordAuthentication; |
42 import java.net.PasswordAuthentication; |
49 import java.net.ServerSocket; |
43 import java.net.ServerSocket; |
50 import java.net.Socket; |
44 import java.net.Socket; |
51 import java.net.SocketAddress; |
|
52 import java.net.URI; |
45 import java.net.URI; |
53 import java.net.URISyntaxException; |
46 import java.net.URISyntaxException; |
54 import java.net.URL; |
47 import java.net.URL; |
55 import java.nio.charset.StandardCharsets; |
48 import java.nio.charset.StandardCharsets; |
56 import java.security.MessageDigest; |
49 import java.security.MessageDigest; |
62 import java.util.List; |
55 import java.util.List; |
63 import java.util.Locale; |
56 import java.util.Locale; |
64 import java.util.Objects; |
57 import java.util.Objects; |
65 import java.util.Optional; |
58 import java.util.Optional; |
66 import java.util.Random; |
59 import java.util.Random; |
|
60 import java.util.StringTokenizer; |
67 import java.util.concurrent.CompletableFuture; |
61 import java.util.concurrent.CompletableFuture; |
68 import java.util.concurrent.CopyOnWriteArrayList; |
62 import java.util.concurrent.CopyOnWriteArrayList; |
69 import java.util.concurrent.atomic.AtomicInteger; |
63 import java.util.concurrent.atomic.AtomicInteger; |
70 import java.util.stream.Collectors; |
64 import java.util.stream.Collectors; |
71 import java.util.stream.Stream; |
65 import java.util.stream.Stream; |
78 * By default this server will echo back whatever is present |
72 * By default this server will echo back whatever is present |
79 * in the request body. Note that the Digest authentication is |
73 * in the request body. Note that the Digest authentication is |
80 * a test implementation implemented only for tests purposes. |
74 * a test implementation implemented only for tests purposes. |
81 * @author danielfuchs |
75 * @author danielfuchs |
82 */ |
76 */ |
83 public class DigestEchoServer implements HttpServerAdapters { |
77 public abstract class DigestEchoServer implements HttpServerAdapters { |
84 |
78 |
85 public static final boolean DEBUG = |
79 public static final boolean DEBUG = |
86 Boolean.parseBoolean(System.getProperty("test.debug", "false")); |
80 Boolean.parseBoolean(System.getProperty("test.debug", "false")); |
87 public enum HttpAuthType { |
81 public enum HttpAuthType { |
88 SERVER, PROXY, SERVER307, PROXY305 |
82 SERVER, PROXY, SERVER307, PROXY305 |
145 final HttpTestServer serverImpl; // this server endpoint |
139 final HttpTestServer serverImpl; // this server endpoint |
146 final DigestEchoServer redirect; // the target server where to redirect 3xx |
140 final DigestEchoServer redirect; // the target server where to redirect 3xx |
147 final HttpTestHandler delegate; // unused |
141 final HttpTestHandler delegate; // unused |
148 final String key; |
142 final String key; |
149 |
143 |
150 private DigestEchoServer(String key, |
144 DigestEchoServer(String key, |
151 HttpTestServer server, |
145 HttpTestServer server, |
152 DigestEchoServer target, |
146 DigestEchoServer target, |
153 HttpTestHandler delegate) { |
147 HttpTestHandler delegate) { |
154 this.key = key; |
148 this.key = key; |
155 this.serverImpl = server; |
149 this.serverImpl = server; |
503 HttpTestServer impl = createHttpServer(version, protocol); |
497 HttpTestServer impl = createHttpServer(version, protocol); |
504 String key = String.format("DigestEchoServer[PID=%s,PORT=%s]:%s:%s:%s:%s", |
498 String key = String.format("DigestEchoServer[PID=%s,PORT=%s]:%s:%s:%s:%s", |
505 ProcessHandle.current().pid(), |
499 ProcessHandle.current().pid(), |
506 impl.getAddress().getPort(), |
500 impl.getAddress().getPort(), |
507 version, protocol, authType, schemeType); |
501 version, protocol, authType, schemeType); |
508 final DigestEchoServer server = new DigestEchoServer(key, impl, null, delegate); |
502 final DigestEchoServer server = new DigestEchoServerImpl(key, impl, null, delegate); |
509 final HttpTestHandler handler = |
503 final HttpTestHandler handler = |
510 server.createHandler(schemeType, auth, authType, false); |
504 server.createHandler(schemeType, auth, authType, false); |
511 HttpTestContext context = impl.addHandler(handler, path); |
505 HttpTestContext context = impl.addHandler(handler, path); |
512 server.configureAuthentication(context, schemeType, auth, authType); |
506 server.configureAuthentication(context, schemeType, auth, authType); |
513 impl.start(); |
507 impl.start(); |
534 ProcessHandle.current().pid(), |
528 ProcessHandle.current().pid(), |
535 impl.getAddress().getPort(), |
529 impl.getAddress().getPort(), |
536 version, protocol, authType, schemeType); |
530 version, protocol, authType, schemeType); |
537 final DigestEchoServer server = "https".equalsIgnoreCase(protocol) |
531 final DigestEchoServer server = "https".equalsIgnoreCase(protocol) |
538 ? new HttpsProxyTunnel(key, impl, null, delegate) |
532 ? new HttpsProxyTunnel(key, impl, null, delegate) |
539 : new DigestEchoServer(key, impl, null, delegate); |
533 : new DigestEchoServerImpl(key, impl, null, delegate); |
540 |
534 |
541 final HttpTestHandler hh = server.createHandler(HttpAuthSchemeType.NONE, |
535 final HttpTestHandler hh = server.createHandler(HttpAuthSchemeType.NONE, |
542 null, HttpAuthType.SERVER, |
536 null, HttpAuthType.SERVER, |
543 server instanceof HttpsProxyTunnel); |
537 server instanceof HttpsProxyTunnel); |
544 HttpTestContext ctxt = impl.addHandler(hh, path); |
538 HttpTestContext ctxt = impl.addHandler(hh, path); |
578 impl.getAddress().getPort(), |
572 impl.getAddress().getPort(), |
579 version, protocol, |
573 version, protocol, |
580 HttpAuthType.SERVER, code300) |
574 HttpAuthType.SERVER, code300) |
581 + "->" + redirectTarget.key; |
575 + "->" + redirectTarget.key; |
582 final DigestEchoServer redirectingServer = |
576 final DigestEchoServer redirectingServer = |
583 new DigestEchoServer(key, impl, redirectTarget, null); |
577 new DigestEchoServerImpl(key, impl, redirectTarget, null); |
584 InetSocketAddress redirectAddr = redirectTarget.getAddress(); |
578 InetSocketAddress redirectAddr = redirectTarget.getAddress(); |
585 URL locationURL = url(targetProtocol, redirectAddr, "/"); |
579 URL locationURL = url(targetProtocol, redirectAddr, "/"); |
586 final HttpTestHandler hh = redirectingServer.create300Handler(key, locationURL, |
580 final HttpTestHandler hh = redirectingServer.create300Handler(key, locationURL, |
587 HttpAuthType.SERVER, code300); |
581 HttpAuthType.SERVER, code300); |
588 impl.addHandler(hh,"/"); |
582 impl.addHandler(hh,"/"); |
589 impl.start(); |
583 impl.start(); |
590 return redirectingServer; |
584 return redirectingServer; |
591 } |
585 } |
592 |
586 |
593 public InetSocketAddress getAddress() { |
587 public abstract InetSocketAddress getServerAddress(); |
594 return new InetSocketAddress("127.0.0.1", |
588 public abstract InetSocketAddress getProxyAddress(); |
595 serverImpl.getAddress().getPort()); |
589 public abstract InetSocketAddress getAddress(); |
596 } |
590 public abstract void stop(); |
597 |
591 public abstract Version getServerVersion(); |
598 public InetSocketAddress getServerAddress() { |
592 |
599 return new InetSocketAddress("127.0.0.1", |
593 private static class DigestEchoServerImpl extends DigestEchoServer { |
600 serverImpl.getAddress().getPort()); |
594 DigestEchoServerImpl(String key, |
601 } |
595 HttpTestServer server, |
602 |
596 DigestEchoServer target, |
603 public InetSocketAddress getProxyAddress() { |
597 HttpTestHandler delegate) { |
604 return new InetSocketAddress("127.0.0.1", |
598 super(key, Objects.requireNonNull(server), target, delegate); |
605 serverImpl.getAddress().getPort()); |
599 } |
606 } |
600 |
607 |
601 public InetSocketAddress getAddress() { |
608 public Version getServerVersion() { return serverImpl.getVersion(); } |
602 return new InetSocketAddress("127.0.0.1", |
609 |
603 serverImpl.getAddress().getPort()); |
610 public void stop() { |
604 } |
611 serverImpl.stop(); |
605 |
612 if (redirect != null) { |
606 public InetSocketAddress getServerAddress() { |
613 redirect.stop(); |
607 return new InetSocketAddress("127.0.0.1", |
|
608 serverImpl.getAddress().getPort()); |
|
609 } |
|
610 |
|
611 public InetSocketAddress getProxyAddress() { |
|
612 return new InetSocketAddress("127.0.0.1", |
|
613 serverImpl.getAddress().getPort()); |
|
614 } |
|
615 |
|
616 public Version getServerVersion() { |
|
617 return serverImpl.getVersion(); |
|
618 } |
|
619 |
|
620 public void stop() { |
|
621 serverImpl.stop(); |
|
622 if (redirect != null) { |
|
623 redirect.stop(); |
|
624 } |
614 } |
625 } |
615 } |
626 } |
616 |
627 |
617 protected void writeResponse(HttpTestExchange he) throws IOException { |
628 protected void writeResponse(HttpTestExchange he) throws IOException { |
618 if (delegate == null) { |
629 if (delegate == null) { |
1418 response.append("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); |
1429 response.append("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); |
1419 return true; |
1430 return true; |
1420 } |
1431 } |
1421 } |
1432 } |
1422 |
1433 |
|
1434 public interface TunnelingProxy { |
|
1435 InetSocketAddress getProxyAddress(); |
|
1436 void stop(); |
|
1437 } |
|
1438 |
1423 // This is a bit hacky: HttpsProxyTunnel is an HTTPTestServer hidden |
1439 // This is a bit hacky: HttpsProxyTunnel is an HTTPTestServer hidden |
1424 // behind a fake proxy that only understands CONNECT requests. |
1440 // behind a fake proxy that only understands CONNECT requests. |
1425 // The fake proxy is just a server socket that intercept the |
1441 // The fake proxy is just a server socket that intercept the |
1426 // CONNECT and then redirect streams to the real server. |
1442 // CONNECT and then redirect streams to the real server. |
1427 static class HttpsProxyTunnel extends DigestEchoServer |
1443 static class HttpsProxyTunnel extends DigestEchoServer |
1428 implements Runnable { |
1444 implements Runnable, TunnelingProxy { |
1429 |
1445 |
1430 final ServerSocket ss; |
1446 final ServerSocket ss; |
1431 final CopyOnWriteArrayList<CompletableFuture<Void>> connectionCFs |
1447 final CopyOnWriteArrayList<CompletableFuture<Void>> connectionCFs |
1432 = new CopyOnWriteArrayList<>(); |
1448 = new CopyOnWriteArrayList<>(); |
1433 volatile ProxyAuthorization authorization; |
1449 volatile ProxyAuthorization authorization; |
1514 }; |
1544 }; |
1515 } |
1545 } |
1516 |
1546 |
1517 @Override |
1547 @Override |
1518 public InetSocketAddress getAddress() { |
1548 public InetSocketAddress getAddress() { |
1519 return new InetSocketAddress(ss.getInetAddress(), ss.getLocalPort()); |
1549 return new InetSocketAddress("127.0.0.1", |
1520 } |
1550 ss.getLocalPort()); |
|
1551 } |
|
1552 @Override |
1521 public InetSocketAddress getProxyAddress() { |
1553 public InetSocketAddress getProxyAddress() { |
1522 return getAddress(); |
1554 return getAddress(); |
1523 } |
1555 } |
|
1556 @Override |
1524 public InetSocketAddress getServerAddress() { |
1557 public InetSocketAddress getServerAddress() { |
1525 return new InetSocketAddress("127.0.0.1", |
1558 // serverImpl can be null if this proxy can serve |
1526 serverImpl.getAddress().getPort()); |
1559 // multiple servers. |
|
1560 if (serverImpl != null) { |
|
1561 return serverImpl.getAddress(); |
|
1562 } |
|
1563 return null; |
1527 } |
1564 } |
1528 |
1565 |
1529 |
1566 |
1530 // This is a bit shaky. It doesn't handle continuation |
1567 // This is a bit shaky. It doesn't handle continuation |
1531 // lines, but our client shouldn't send any. |
1568 // lines, but our client shouldn't send any. |
1572 System.out.println(now() + "Tunnel: Request line: " + requestLine); |
1609 System.out.println(now() + "Tunnel: Request line: " + requestLine); |
1573 if (requestLine.startsWith("CONNECT ")) { |
1610 if (requestLine.startsWith("CONNECT ")) { |
1574 // We should probably check that the next word following |
1611 // We should probably check that the next word following |
1575 // CONNECT is the host:port of our HTTPS serverImpl. |
1612 // CONNECT is the host:port of our HTTPS serverImpl. |
1576 // Some improvement for a followup! |
1613 // Some improvement for a followup! |
|
1614 StringTokenizer tokenizer = new StringTokenizer(requestLine); |
|
1615 String connect = tokenizer.nextToken(); |
|
1616 assert connect.equalsIgnoreCase("connect"); |
|
1617 String hostport = tokenizer.nextToken(); |
|
1618 InetSocketAddress targetAddress; |
|
1619 try { |
|
1620 URI uri = new URI("https", hostport, "/", null, null); |
|
1621 int port = uri.getPort(); |
|
1622 port = port == -1 ? 443 : port; |
|
1623 targetAddress = new InetSocketAddress(uri.getHost(), port); |
|
1624 if (serverImpl != null) { |
|
1625 assert targetAddress.getHostString() |
|
1626 .equalsIgnoreCase(serverImpl.getAddress().getHostString()); |
|
1627 assert targetAddress.getPort() == serverImpl.getAddress().getPort(); |
|
1628 } |
|
1629 } catch (Throwable x) { |
|
1630 System.err.printf("Bad target address: \"%s\" in \"%s\"%n", |
|
1631 hostport, requestLine); |
|
1632 toClose.close(); |
|
1633 continue; |
|
1634 } |
1577 |
1635 |
1578 // Read all headers until we find the empty line that |
1636 // Read all headers until we find the empty line that |
1579 // signals the end of all headers. |
1637 // signals the end of all headers. |
1580 String line = requestLine; |
1638 String line = requestLine; |
1581 while(!line.equals("")) { |
1639 while(!line.equals("")) { |
1593 pw.print(response.toString()); |
1651 pw.print(response.toString()); |
1594 pw.flush(); |
1652 pw.flush(); |
1595 toClose.close(); |
1653 toClose.close(); |
1596 continue; |
1654 continue; |
1597 } |
1655 } |
|
1656 System.out.println(now() |
|
1657 + "Tunnel connecting to target server at " |
|
1658 + targetAddress.getAddress() + ":" + targetAddress.getPort()); |
1598 targetConnection = new Socket( |
1659 targetConnection = new Socket( |
1599 serverImpl.getAddress().getAddress(), |
1660 targetAddress.getAddress(), |
1600 serverImpl.getAddress().getPort()); |
1661 targetAddress.getPort()); |
1601 |
1662 |
1602 // Then send the 200 OK response to the client |
1663 // Then send the 200 OK response to the client |
1603 System.out.println(now() + "Tunnel: Sending " |
1664 System.out.println(now() + "Tunnel: Sending " |
1604 + response); |
1665 + response); |
1605 pw.print(response); |
1666 pw.print(response); |
1657 connectionCFs.forEach(cf -> cf.complete(null)); |
1718 connectionCFs.forEach(cf -> cf.complete(null)); |
1658 } |
1719 } |
1659 } |
1720 } |
1660 } |
1721 } |
1661 |
1722 |
|
1723 /** |
|
1724 * Creates a TunnelingProxy that can serve multiple servers. |
|
1725 * The server address is extracted from the CONNECT request line. |
|
1726 * @param authScheme The authentication scheme supported by the proxy. |
|
1727 * Typically one of DIGEST, BASIC, NONE. |
|
1728 * @return A new TunnelingProxy able to serve multiple servers. |
|
1729 * @throws IOException If the proxy could not be created. |
|
1730 */ |
|
1731 public static TunnelingProxy createHttpsProxyTunnel(HttpAuthSchemeType authScheme) |
|
1732 throws IOException { |
|
1733 HttpsProxyTunnel result = new HttpsProxyTunnel("", null, null, null); |
|
1734 if (authScheme != HttpAuthSchemeType.NONE) { |
|
1735 result.configureAuthentication(null, |
|
1736 authScheme, |
|
1737 AUTHENTICATOR, |
|
1738 HttpAuthType.PROXY); |
|
1739 } |
|
1740 return result; |
|
1741 } |
|
1742 |
1662 private static String protocol(String protocol) { |
1743 private static String protocol(String protocol) { |
1663 if ("http".equalsIgnoreCase(protocol)) return "http"; |
1744 if ("http".equalsIgnoreCase(protocol)) return "http"; |
1664 else if ("https".equalsIgnoreCase(protocol)) return "https"; |
1745 else if ("https".equalsIgnoreCase(protocol)) return "https"; |
1665 else throw new InternalError("Unsupported protocol: " + protocol); |
1746 else throw new InternalError("Unsupported protocol: " + protocol); |
1666 } |
1747 } |