Path: blob/master/test/jdk/sun/net/www/protocol/https/HttpsURLConnection/TunnelProxy.java
41161 views
/*1* Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/2223/*24*25*/2627import java.net.*;28import java.io.*;29import java.nio.*;30import java.nio.channels.*;31import sun.net.www.MessageHeader;32import java.util.*;3334public class TunnelProxy {3536ServerSocketChannel schan;37int threads;38int cperthread;39Server[] servers;4041/**42* Create a <code>TunnelProxy<code> instance with the specified callback object43* for handling requests. One thread is created to handle requests,44* and up to ten TCP connections will be handled simultaneously.45* @param cb the callback object which is invoked to handle each46* incoming request47*/4849public TunnelProxy () throws IOException {50this (1, 10, 0);51}5253/**54* Create a <code>TunnelProxy<code> instance with the specified number of55* threads and maximum number of connections per thread. This functions56* the same as the 4 arg constructor, where the port argument is set to zero.57* @param cb the callback object which is invoked to handle each58* incoming request59* @param threads the number of threads to create to handle requests60* in parallel61* @param cperthread the number of simultaneous TCP connections to62* handle per thread63*/6465public TunnelProxy (int threads, int cperthread)66throws IOException {67this (threads, cperthread, 0);68}6970/**71* Create a <code>TunnelProxy<code> instance with the specified number72* of threads and maximum number of connections per thread and running on73* the specified port. The specified number of threads are created to74* handle incoming requests, and each thread is allowed75* to handle a number of simultaneous TCP connections.76* @param cb the callback object which is invoked to handle77* each incoming request78* @param threads the number of threads to create to handle79* requests in parallel80* @param cperthread the number of simultaneous TCP connections81* to handle per thread82* @param port the port number to bind the server to. <code>Zero</code>83* means choose any free port.84*/8586public TunnelProxy (int threads, int cperthread, int port)87throws IOException {88this(threads, cperthread, null, 0);89}9091/**92* Create a <code>TunnelProxy<code> instance with the specified number93* of threads and maximum number of connections per thread and running on94* the specified port. The specified number of threads are created to95* handle incoming requests, and each thread is allowed96* to handle a number of simultaneous TCP connections.97* @param cb the callback object which is invoked to handle98* each incoming request99* @param threads the number of threads to create to handle100* requests in parallel101* @param cperthread the number of simultaneous TCP connections102* to handle per thread103* @param address the address to bind to. null means all addresses.104* @param port the port number to bind the server to. <code>Zero</code>105* means choose any free port.106*/107public TunnelProxy (int threads, int cperthread, InetAddress address, int port)108throws IOException {109schan = ServerSocketChannel.open ();110InetSocketAddress addr = new InetSocketAddress (address, port);111schan.socket().bind (addr);112this.threads = threads;113this.cperthread = cperthread;114servers = new Server [threads];115for (int i=0; i<threads; i++) {116servers[i] = new Server (schan, cperthread);117servers[i].start();118}119}120121/** Tell all threads in the server to exit within 5 seconds.122* This is an abortive termination. Just prior to the thread exiting123* all channels in that thread waiting to be closed are forceably closed.124*/125126public void terminate () {127for (int i=0; i<threads; i++) {128servers[i].terminate ();129}130}131132/**133* return the local port number to which the server is bound.134* @return the local port number135*/136137public int getLocalPort () {138return schan.socket().getLocalPort ();139}140141static class Server extends Thread {142143ServerSocketChannel schan;144Selector selector;145SelectionKey listenerKey;146SelectionKey key; /* the current key being processed */147ByteBuffer consumeBuffer;148int maxconn;149int nconn;150ClosedChannelList clist;151boolean shutdown;152Pipeline pipe1 = null;153Pipeline pipe2 = null;154155Server (ServerSocketChannel schan, int maxconn) {156this.schan = schan;157this.maxconn = maxconn;158nconn = 0;159consumeBuffer = ByteBuffer.allocate (512);160clist = new ClosedChannelList ();161try {162selector = Selector.open ();163schan.configureBlocking (false);164listenerKey = schan.register (selector, SelectionKey.OP_ACCEPT);165} catch (IOException e) {166System.err.println ("Server could not start: " + e);167}168}169170/* Stop the thread as soon as possible */171public synchronized void terminate () {172shutdown = true;173if (pipe1 != null) pipe1.terminate();174if (pipe2 != null) pipe2.terminate();175}176177public void run () {178try {179while (true) {180selector.select (1000);181Set selected = selector.selectedKeys();182Iterator iter = selected.iterator();183while (iter.hasNext()) {184key = (SelectionKey)iter.next();185if (key.equals (listenerKey)) {186SocketChannel sock = schan.accept ();187if (sock == null) {188/* false notification */189iter.remove();190continue;191}192sock.configureBlocking (false);193sock.register (selector, SelectionKey.OP_READ);194nconn ++;195if (nconn == maxconn) {196/* deregister */197listenerKey.cancel ();198listenerKey = null;199}200} else {201if (key.isReadable()) {202boolean closed;203SocketChannel chan = (SocketChannel) key.channel();204if (key.attachment() != null) {205closed = consume (chan);206} else {207closed = read (chan, key);208}209if (closed) {210chan.close ();211key.cancel ();212if (nconn == maxconn) {213listenerKey = schan.register (selector, SelectionKey.OP_ACCEPT);214}215nconn --;216}217}218}219iter.remove();220}221clist.check();222if (shutdown) {223clist.terminate ();224return;225}226}227} catch (IOException e) {228System.out.println ("Server exception: " + e);229// TODO finish230}231}232233/* read all the data off the channel without looking at it234* return true if connection closed235*/236boolean consume (SocketChannel chan) {237try {238consumeBuffer.clear ();239int c = chan.read (consumeBuffer);240if (c == -1)241return true;242} catch (IOException e) {243return true;244}245return false;246}247248/* return true if the connection is closed, false otherwise */249250private boolean read (SocketChannel chan, SelectionKey key) {251HttpTransaction msg;252boolean res;253try {254InputStream is = new BufferedInputStream (new NioInputStream (chan));255String requestline = readLine (is);256MessageHeader mhead = new MessageHeader (is);257String[] req = requestline.split (" ");258if (req.length < 2) {259/* invalid request line */260return false;261}262String cmd = req[0];263URI uri = null;264if (!("CONNECT".equalsIgnoreCase(cmd))) {265// we expect CONNECT command266return false;267}268try {269uri = new URI("http://" + req[1]);270} catch (URISyntaxException e) {271System.err.println ("Invalid URI: " + e);272res = true;273}274275// CONNECT ack276OutputStream os = new BufferedOutputStream(new NioOutputStream(chan));277byte[] ack = "HTTP/1.1 200 Connection established\r\n\r\n".getBytes();278os.write(ack, 0, ack.length);279os.flush();280281// tunnel anything else282tunnel(is, os, uri);283284res = false;285} catch (IOException e) {286res = true;287}288return res;289}290291private void tunnel(InputStream fromClient, OutputStream toClient, URI serverURI) throws IOException {292Socket sockToServer = new Socket(serverURI.getHost(), serverURI.getPort());293OutputStream toServer = sockToServer.getOutputStream();294InputStream fromServer = sockToServer.getInputStream();295296pipe1 = new Pipeline(fromClient, toServer);297pipe2 = new Pipeline(fromServer, toClient);298// start pump299pipe1.start();300pipe2.start();301// wait them to end302try {303pipe1.join();304} catch (InterruptedException e) {305// No-op306} finally {307sockToServer.close();308}309}310311private String readLine (InputStream is) throws IOException {312boolean done=false, readCR=false;313byte[] b = new byte [512];314int c, l = 0;315316while (!done) {317c = is.read ();318if (c == '\n' && readCR) {319done = true;320} else {321if (c == '\r' && !readCR) {322readCR = true;323} else {324b[l++] = (byte)c;325}326}327}328return new String (b);329}330331/** close the channel associated with the current key by:332* 1. shutdownOutput (send a FIN)333* 2. mark the key so that incoming data is to be consumed and discarded334* 3. After a period, close the socket335*/336337synchronized void orderlyCloseChannel (SelectionKey key) throws IOException {338SocketChannel ch = (SocketChannel)key.channel ();339ch.socket().shutdownOutput();340key.attach (this);341clist.add (key);342}343344synchronized void abortiveCloseChannel (SelectionKey key) throws IOException {345SocketChannel ch = (SocketChannel)key.channel ();346Socket s = ch.socket ();347s.setSoLinger (true, 0);348ch.close();349}350}351352353/**354* Implements blocking reading semantics on top of a non-blocking channel355*/356357static class NioInputStream extends InputStream {358SocketChannel channel;359Selector selector;360ByteBuffer chanbuf;361SelectionKey key;362int available;363byte[] one;364boolean closed;365ByteBuffer markBuf; /* reads may be satisifed from this buffer */366boolean marked;367boolean reset;368int readlimit;369370public NioInputStream (SocketChannel chan) throws IOException {371this.channel = chan;372selector = Selector.open();373chanbuf = ByteBuffer.allocate (1024);374key = chan.register (selector, SelectionKey.OP_READ);375available = 0;376one = new byte[1];377closed = marked = reset = false;378}379380public synchronized int read (byte[] b) throws IOException {381return read (b, 0, b.length);382}383384public synchronized int read () throws IOException {385return read (one, 0, 1);386}387388public synchronized int read (byte[] b, int off, int srclen) throws IOException {389390int canreturn, willreturn;391392if (closed)393return -1;394395if (reset) { /* satisfy from markBuf */396canreturn = markBuf.remaining ();397willreturn = canreturn>srclen ? srclen : canreturn;398markBuf.get(b, off, willreturn);399if (canreturn == willreturn) {400reset = false;401}402} else { /* satisfy from channel */403canreturn = available();404if (canreturn == 0) {405block ();406canreturn = available();407}408willreturn = canreturn>srclen ? srclen : canreturn;409chanbuf.get(b, off, willreturn);410available -= willreturn;411412if (marked) { /* copy into markBuf */413try {414markBuf.put (b, off, willreturn);415} catch (BufferOverflowException e) {416marked = false;417}418}419}420return willreturn;421}422423public synchronized int available () throws IOException {424if (closed)425throw new IOException ("Stream is closed");426427if (reset)428return markBuf.remaining();429430if (available > 0)431return available;432433chanbuf.clear ();434available = channel.read (chanbuf);435if (available > 0)436chanbuf.flip();437else if (available == -1)438throw new IOException ("Stream is closed");439return available;440}441442/**443* block() only called when available==0 and buf is empty444*/445private synchronized void block () throws IOException {446//assert available == 0;447int n = selector.select ();448//assert n == 1;449selector.selectedKeys().clear();450available ();451}452453public void close () throws IOException {454if (closed)455return;456channel.close ();457closed = true;458}459460public synchronized void mark (int readlimit) {461if (closed)462return;463this.readlimit = readlimit;464markBuf = ByteBuffer.allocate (readlimit);465marked = true;466reset = false;467}468469public synchronized void reset () throws IOException {470if (closed )471return;472if (!marked)473throw new IOException ("Stream not marked");474marked = false;475reset = true;476markBuf.flip ();477}478}479480static class NioOutputStream extends OutputStream {481SocketChannel channel;482ByteBuffer buf;483SelectionKey key;484Selector selector;485boolean closed;486byte[] one;487488public NioOutputStream (SocketChannel channel) throws IOException {489this.channel = channel;490selector = Selector.open ();491key = channel.register (selector, SelectionKey.OP_WRITE);492closed = false;493one = new byte [1];494}495496public synchronized void write (int b) throws IOException {497one[0] = (byte)b;498write (one, 0, 1);499}500501public synchronized void write (byte[] b) throws IOException {502write (b, 0, b.length);503}504505public synchronized void write (byte[] b, int off, int len) throws IOException {506if (closed)507throw new IOException ("stream is closed");508509buf = ByteBuffer.allocate (len);510buf.put (b, off, len);511buf.flip ();512int n;513while ((n = channel.write (buf)) < len) {514len -= n;515if (len == 0)516return;517selector.select ();518selector.selectedKeys().clear ();519}520}521522public void close () throws IOException {523if (closed)524return;525channel.close ();526closed = true;527}528}529530/*531* Pipeline object :-532* 1) Will pump every byte from its input stream to output stream533* 2) Is an 'active object'534*/535static class Pipeline implements Runnable {536InputStream in;537OutputStream out;538Thread t;539540public Pipeline(InputStream is, OutputStream os) {541in = is;542out = os;543}544545public void start() {546t = new Thread(this);547t.start();548}549550public void join() throws InterruptedException {551t.join();552}553554public void terminate() {555t.interrupt();556}557558public void run() {559byte[] buffer = new byte[10000];560try {561while (!Thread.interrupted()) {562int len;563while ((len = in.read(buffer)) != -1) {564out.write(buffer, 0, len);565out.flush();566}567}568} catch(IOException e) {569// No-op570} finally {571}572}573}574575/**576* Utilities for synchronization. A condition is577* identified by a string name, and is initialized578* upon first use (ie. setCondition() or waitForCondition()). Threads579* are blocked until some thread calls (or has called) setCondition() for the same580* condition.581* <P>582* A rendezvous built on a condition is also provided for synchronizing583* N threads.584*/585586private static HashMap conditions = new HashMap();587588/*589* Modifiable boolean object590*/591private static class BValue {592boolean v;593}594595/*596* Modifiable int object597*/598private static class IValue {599int v;600IValue (int i) {601v =i;602}603}604605606private static BValue getCond (String condition) {607synchronized (conditions) {608BValue cond = (BValue) conditions.get (condition);609if (cond == null) {610cond = new BValue();611conditions.put (condition, cond);612}613return cond;614}615}616617/**618* Set the condition to true. Any threads that are currently blocked619* waiting on the condition, will be unblocked and allowed to continue.620* Threads that subsequently call waitForCondition() will not block.621* If the named condition did not exist prior to the call, then it is created622* first.623*/624625public static void setCondition (String condition) {626BValue cond = getCond (condition);627synchronized (cond) {628if (cond.v) {629return;630}631cond.v = true;632cond.notifyAll();633}634}635636/**637* If the named condition does not exist, then it is created and initialized638* to false. If the condition exists or has just been created and its value639* is false, then the thread blocks until another thread sets the condition.640* If the condition exists and is already set to true, then this call returns641* immediately without blocking.642*/643644public static void waitForCondition (String condition) {645BValue cond = getCond (condition);646synchronized (cond) {647if (!cond.v) {648try {649cond.wait();650} catch (InterruptedException e) {}651}652}653}654655/* conditions must be locked when accessing this */656static HashMap rv = new HashMap();657658/**659* Force N threads to rendezvous (ie. wait for each other) before proceeding.660* The first thread(s) to call are blocked until the last661* thread makes the call. Then all threads continue.662* <p>663* All threads that call with the same condition name, must use the same value664* for N (or the results may be not be as expected).665* <P>666* Obviously, if fewer than N threads make the rendezvous then the result667* will be a hang.668*/669670public static void rendezvous (String condition, int N) {671BValue cond;672IValue iv;673String name = "RV_"+condition;674675/* get the condition */676677synchronized (conditions) {678cond = (BValue)conditions.get (name);679if (cond == null) {680/* we are first caller */681if (N < 2) {682throw new RuntimeException ("rendezvous must be called with N >= 2");683}684cond = new BValue ();685conditions.put (name, cond);686iv = new IValue (N-1);687rv.put (name, iv);688} else {689/* already initialised, just decrement the counter */690iv = (IValue) rv.get (name);691iv.v --;692}693}694695if (iv.v > 0) {696waitForCondition (name);697} else {698setCondition (name);699synchronized (conditions) {700clearCondition (name);701rv.remove (name);702}703}704}705706/**707* If the named condition exists and is set then remove it, so it can708* be re-initialized and used again. If the condition does not exist, or709* exists but is not set, then the call returns without doing anything.710* Note, some higher level synchronization711* may be needed between clear and the other operations.712*/713714public static void clearCondition(String condition) {715BValue cond;716synchronized (conditions) {717cond = (BValue) conditions.get (condition);718if (cond == null) {719return;720}721synchronized (cond) {722if (cond.v) {723conditions.remove (condition);724}725}726}727}728}729730731