Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/test/sun/net/www/protocol/https/TestHttpsServer.java
38867 views
/*1* Copyright (c) 2002, 2012, 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*/2223import java.net.*;24import java.io.*;25import java.nio.*;26import java.nio.channels.*;27import sun.net.www.MessageHeader;28import java.util.*;29import javax.net.ssl.*;30import javax.net.ssl.SSLEngineResult.*;31import java.security.*;3233/**34* This class implements a simple HTTPS server. It uses multiple threads to35* handle connections in parallel, and will spin off a new thread to handle36* each request. (this is easier to implement with SSLEngine)37* <p>38* It must be instantiated with a {@link HttpCallback} object to which39* requests are given and must be handled.40* <p>41* Simple synchronization between the client(s) and server can be done42* using the {@link #waitForCondition(String)}, {@link #setCondition(String)} and43* {@link #rendezvous(String,int)} methods.44*45* NOTE NOTE NOTE NOTE NOTE NOTE NOTE46*47* If you make a change in here, please don't forget to make the48* corresponding change in the J2SE equivalent.49*50* NOTE NOTE NOTE NOTE NOTE NOTE NOTE51*/5253public class TestHttpsServer {5455ServerSocketChannel schan;56int threads;57int cperthread;58HttpCallback cb;59Server[] servers;6061// ssl related fields62static SSLContext sslCtx;6364/**65* Create a <code>TestHttpsServer<code> instance with the specified callback object66* for handling requests. One thread is created to handle requests,67* and up to ten TCP connections will be handled simultaneously.68* @param cb the callback object which is invoked to handle each69* incoming request70*/7172public TestHttpsServer (HttpCallback cb) throws IOException {73this (cb, 1, 10, 0);74}7576/**77* Create a <code>TestHttpsServer<code> instance with the specified number of78* threads and maximum number of connections per thread. This functions79* the same as the 4 arg constructor, where the port argument is set to zero.80* @param cb the callback object which is invoked to handle each81* incoming request82* @param threads the number of threads to create to handle requests83* in parallel84* @param cperthread the number of simultaneous TCP connections to85* handle per thread86*/8788public TestHttpsServer (HttpCallback cb, int threads, int cperthread)89throws IOException {90this (cb, threads, cperthread, 0);91}9293/**94* Create a <code>TestHttpsServer<code> instance with the specified number95* of threads and maximum number of connections per thread and running on96* the specified port. The specified number of threads are created to97* handle incoming requests, and each thread is allowed98* to handle a number of simultaneous TCP connections.99* @param cb the callback object which is invoked to handle100* each incoming request101* @param threads the number of threads to create to handle102* requests in parallel103* @param cperthread the number of simultaneous TCP connections104* to handle per thread105* @param port the port number to bind the server to. <code>Zero</code>106* means choose any free port.107*/108109public TestHttpsServer (HttpCallback cb, int threads, int cperthread, int port)110throws IOException {111schan = ServerSocketChannel.open ();112InetSocketAddress addr = new InetSocketAddress (port);113schan.socket().bind (addr);114this.threads = threads;115this.cb = cb;116this.cperthread = cperthread;117118try {119// create and initialize a SSLContext120KeyStore ks = KeyStore.getInstance("JKS");121KeyStore ts = KeyStore.getInstance("JKS");122char[] passphrase = "passphrase".toCharArray();123124ks.load(new FileInputStream(System.getProperty("javax.net.ssl.keyStore")), passphrase);125ts.load(new FileInputStream(System.getProperty("javax.net.ssl.trustStore")), passphrase);126127KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");128kmf.init(ks, passphrase);129130TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");131tmf.init(ts);132133sslCtx = SSLContext.getInstance("TLS");134135sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);136137servers = new Server [threads];138for (int i=0; i<threads; i++) {139servers[i] = new Server (cb, schan, cperthread);140servers[i].start();141}142} catch (Exception ex) {143throw new RuntimeException("test failed. cause: "+ex.getMessage());144}145}146147/** Tell all threads in the server to exit within 5 seconds.148* This is an abortive termination. Just prior to the thread exiting149* all channels in that thread waiting to be closed are forceably closed.150*/151152public void terminate () {153for (int i=0; i<threads; i++) {154servers[i].terminate ();155}156}157158/**159* return the local port number to which the server is bound.160* @return the local port number161*/162163public int getLocalPort () {164return schan.socket().getLocalPort ();165}166167static class Server extends Thread {168169ServerSocketChannel schan;170Selector selector;171SelectionKey listenerKey;172SelectionKey key; /* the current key being processed */173HttpCallback cb;174ByteBuffer consumeBuffer;175int maxconn;176int nconn;177ClosedChannelList clist;178boolean shutdown;179180Server (HttpCallback cb, ServerSocketChannel schan, int maxconn) {181this.schan = schan;182this.maxconn = maxconn;183this.cb = cb;184nconn = 0;185consumeBuffer = ByteBuffer.allocate (512);186clist = new ClosedChannelList ();187try {188selector = Selector.open ();189schan.configureBlocking (false);190listenerKey = schan.register (selector, SelectionKey.OP_ACCEPT);191} catch (IOException e) {192System.err.println ("Server could not start: " + e);193}194}195196/* Stop the thread as soon as possible */197public synchronized void terminate () {198shutdown = true;199}200201public void run () {202try {203while (true) {204selector.select (1000);205Set selected = selector.selectedKeys();206Iterator iter = selected.iterator();207while (iter.hasNext()) {208key = (SelectionKey)iter.next();209if (key.equals (listenerKey)) {210SocketChannel sock = schan.accept ();211if (sock == null) {212/* false notification */213iter.remove();214continue;215}216sock.configureBlocking (true);217SSLEngine sslEng = sslCtx.createSSLEngine();218sslEng.setUseClientMode(false);219new ServerWorker(cb, sock, sslEng).start();220nconn ++;221if (nconn == maxconn) {222/* deregister */223listenerKey.cancel ();224listenerKey = null;225}226} else {227if (key.isReadable()) {228boolean closed = false;229SocketChannel chan = (SocketChannel) key.channel();230if (key.attachment() != null) {231closed = consume (chan);232}233234if (closed) {235chan.close ();236key.cancel ();237if (nconn == maxconn) {238listenerKey = schan.register (selector, SelectionKey.OP_ACCEPT);239}240nconn --;241}242}243}244iter.remove();245}246clist.check();247248synchronized (this) {249if (shutdown) {250clist.terminate ();251return;252}253}254}255} catch (IOException e) {256System.out.println ("Server exception: " + e);257// TODO finish258}259}260261/* read all the data off the channel without looking at it262* return true if connection closed263*/264boolean consume (SocketChannel chan) {265try {266consumeBuffer.clear ();267int c = chan.read (consumeBuffer);268if (c == -1)269return true;270} catch (IOException e) {271return true;272}273return false;274}275}276277static class ServerWorker extends Thread {278private ByteBuffer inNetBB;279private ByteBuffer outNetBB;280private ByteBuffer inAppBB;281private ByteBuffer outAppBB;282283SSLEngine sslEng;284SocketChannel schan;285HttpCallback cb;286HandshakeStatus currentHSStatus;287boolean initialHSComplete;288/*289* All inbound data goes through this buffer.290*291* It might be nice to use a cache of ByteBuffers so we're292* not alloc/dealloc'ing all over the place.293*/294295/*296* Application buffers, also used for handshaking297*/298private int appBBSize;299300ServerWorker (HttpCallback cb, SocketChannel schan, SSLEngine sslEng) {301this.sslEng = sslEng;302this.schan = schan;303this.cb = cb;304currentHSStatus = HandshakeStatus.NEED_UNWRAP;305initialHSComplete = false;306int netBBSize = sslEng.getSession().getPacketBufferSize();307inNetBB = ByteBuffer.allocate(netBBSize);308outNetBB = ByteBuffer.allocate(netBBSize);309appBBSize = sslEng.getSession().getApplicationBufferSize();310inAppBB = ByteBuffer.allocate(appBBSize);311outAppBB = ByteBuffer.allocate(appBBSize);312}313314public SSLEngine getSSLEngine() {315return sslEng;316}317318public ByteBuffer outNetBB() {319return outNetBB;320}321322public ByteBuffer outAppBB() {323return outAppBB;324}325326public void run () {327try {328SSLEngineResult result;329330while (!initialHSComplete) {331332switch (currentHSStatus) {333334case NEED_UNWRAP:335int bytes = schan.read(inNetBB);336337needIO:338while (currentHSStatus == HandshakeStatus.NEED_UNWRAP) {339/*340* Don't need to resize requestBB, since no app data should341* be generated here.342*/343inNetBB.flip();344result = sslEng.unwrap(inNetBB, inAppBB);345inNetBB.compact();346currentHSStatus = result.getHandshakeStatus();347348switch (result.getStatus()) {349350case OK:351switch (currentHSStatus) {352case NOT_HANDSHAKING:353throw new IOException(354"Not handshaking during initial handshake");355356case NEED_TASK:357Runnable task;358while ((task = sslEng.getDelegatedTask()) != null) {359task.run();360currentHSStatus = sslEng.getHandshakeStatus();361}362break;363}364365break;366367case BUFFER_UNDERFLOW:368break needIO;369370default: // BUFFER_OVERFLOW/CLOSED:371throw new IOException("Received" + result.getStatus() +372"during initial handshaking");373}374}375376/*377* Just transitioned from read to write.378*/379if (currentHSStatus != HandshakeStatus.NEED_WRAP) {380break;381}382383// Fall through and fill the write buffer.384385case NEED_WRAP:386/*387* The flush above guarantees the out buffer to be empty388*/389outNetBB.clear();390result = sslEng.wrap(inAppBB, outNetBB);391outNetBB.flip();392schan.write (outNetBB);393outNetBB.compact();394currentHSStatus = result.getHandshakeStatus();395396switch (result.getStatus()) {397case OK:398399if (currentHSStatus == HandshakeStatus.NEED_TASK) {400Runnable task;401while ((task = sslEng.getDelegatedTask()) != null) {402task.run();403currentHSStatus = sslEng.getHandshakeStatus();404}405}406407break;408409default: // BUFFER_OVERFLOW/BUFFER_UNDERFLOW/CLOSED:410throw new IOException("Received" + result.getStatus() +411"during initial handshaking");412}413break;414415case FINISHED:416initialHSComplete = true;417break;418default: // NOT_HANDSHAKING/NEED_TASK419throw new RuntimeException("Invalid Handshaking State" +420currentHSStatus);421} // switch422}423// read the application data; using non-blocking mode424schan.configureBlocking(false);425read(schan, sslEng);426} catch (Exception ex) {427throw new RuntimeException(ex);428}429}430431/* return true if the connection is closed, false otherwise */432433private boolean read (SocketChannel chan, SSLEngine sslEng) {434HttpTransaction msg;435boolean res;436try {437InputStream is = new BufferedInputStream (new NioInputStream (chan, sslEng, inNetBB, inAppBB));438String requestline = readLine (is);439MessageHeader mhead = new MessageHeader (is);440String clen = mhead.findValue ("Content-Length");441String trferenc = mhead.findValue ("Transfer-Encoding");442String data = null;443if (trferenc != null && trferenc.equals ("chunked"))444data = new String (readChunkedData (is));445else if (clen != null)446data = new String (readNormalData (is, Integer.parseInt (clen)));447String[] req = requestline.split (" ");448if (req.length < 2) {449/* invalid request line */450return false;451}452String cmd = req[0];453URI uri = null;454try {455uri = new URI (req[1]);456msg = new HttpTransaction (this, cmd, uri, mhead, data, null, chan);457cb.request (msg);458} catch (URISyntaxException e) {459System.err.println ("Invalid URI: " + e);460msg = new HttpTransaction (this, cmd, null, null, null, null, chan);461msg.sendResponse (501, "Whatever");462}463res = false;464} catch (IOException e) {465res = true;466}467return res;468}469470byte[] readNormalData (InputStream is, int len) throws IOException {471byte [] buf = new byte [len];472int c, off=0, remain=len;473while (remain > 0 && ((c=is.read (buf, off, remain))>0)) {474remain -= c;475off += c;476}477return buf;478}479480private void readCRLF(InputStream is) throws IOException {481int cr = is.read();482int lf = is.read();483484if (((cr & 0xff) != 0x0d) ||485((lf & 0xff) != 0x0a)) {486throw new IOException(487"Expected <CR><LF>: got '" + cr + "/" + lf + "'");488}489}490491byte[] readChunkedData (InputStream is) throws IOException {492LinkedList l = new LinkedList ();493int total = 0;494for (int len=readChunkLen(is); len!=0; len=readChunkLen(is)) {495l.add (readNormalData(is, len));496total += len;497readCRLF(is); // CRLF at end of chunk498}499readCRLF(is); // CRLF at end of Chunked Stream.500byte[] buf = new byte [total];501Iterator i = l.iterator();502int x = 0;503while (i.hasNext()) {504byte[] b = (byte[])i.next();505System.arraycopy (b, 0, buf, x, b.length);506x += b.length;507}508return buf;509}510511private int readChunkLen (InputStream is) throws IOException {512int c, len=0;513boolean done=false, readCR=false;514while (!done) {515c = is.read ();516if (c == '\n' && readCR) {517done = true;518} else {519if (c == '\r' && !readCR) {520readCR = true;521} else {522int x=0;523if (c >= 'a' && c <= 'f') {524x = c - 'a' + 10;525} else if (c >= 'A' && c <= 'F') {526x = c - 'A' + 10;527} else if (c >= '0' && c <= '9') {528x = c - '0';529}530len = len * 16 + x;531}532}533}534return len;535}536537private String readLine (InputStream is) throws IOException {538boolean done=false, readCR=false;539byte[] b = new byte [512];540int c, l = 0;541542while (!done) {543c = is.read ();544if (c == '\n' && readCR) {545done = true;546} else {547if (c == '\r' && !readCR) {548readCR = true;549} else {550b[l++] = (byte)c;551}552}553}554return new String (b);555}556557/** close the channel associated with the current key by:558* 1. shutdownOutput (send a FIN)559* 2. mark the key so that incoming data is to be consumed and discarded560* 3. After a period, close the socket561*/562563synchronized void orderlyCloseChannel (SocketChannel ch) throws IOException {564ch.socket().shutdownOutput();565}566567synchronized void abortiveCloseChannel (SocketChannel ch) throws IOException {568Socket s = ch.socket ();569s.setSoLinger (true, 0);570ch.close();571}572}573574575/**576* Implements blocking reading semantics on top of a non-blocking channel577*/578579static class NioInputStream extends InputStream {580SSLEngine sslEng;581SocketChannel channel;582Selector selector;583ByteBuffer inNetBB;584ByteBuffer inAppBB;585SelectionKey key;586int available;587byte[] one;588boolean closed;589ByteBuffer markBuf; /* reads may be satisifed from this buffer */590boolean marked;591boolean reset;592int readlimit;593594public NioInputStream (SocketChannel chan, SSLEngine sslEng, ByteBuffer inNetBB, ByteBuffer inAppBB) throws IOException {595this.sslEng = sslEng;596this.channel = chan;597selector = Selector.open();598this.inNetBB = inNetBB;599this.inAppBB = inAppBB;600key = chan.register (selector, SelectionKey.OP_READ);601available = 0;602one = new byte[1];603closed = marked = reset = false;604}605606public synchronized int read (byte[] b) throws IOException {607return read (b, 0, b.length);608}609610public synchronized int read () throws IOException {611return read (one, 0, 1);612}613614public synchronized int read (byte[] b, int off, int srclen) throws IOException {615616int canreturn, willreturn;617618if (closed)619return -1;620621if (reset) { /* satisfy from markBuf */622canreturn = markBuf.remaining ();623willreturn = canreturn>srclen ? srclen : canreturn;624markBuf.get(b, off, willreturn);625if (canreturn == willreturn) {626reset = false;627}628} else { /* satisfy from channel */629canreturn = available();630if (canreturn == 0) {631block ();632canreturn = available();633}634willreturn = canreturn>srclen ? srclen : canreturn;635inAppBB.get(b, off, willreturn);636available -= willreturn;637638if (marked) { /* copy into markBuf */639try {640markBuf.put (b, off, willreturn);641} catch (BufferOverflowException e) {642marked = false;643}644}645}646return willreturn;647}648649public synchronized int available () throws IOException {650if (closed)651throw new IOException ("Stream is closed");652653if (reset)654return markBuf.remaining();655656if (available > 0)657return available;658659inAppBB.clear ();660int bytes = channel.read (inNetBB);661662int needed = sslEng.getSession().getApplicationBufferSize();663if (needed > inAppBB.remaining()) {664inAppBB = ByteBuffer.allocate(needed);665}666inNetBB.flip();667SSLEngineResult result = sslEng.unwrap(inNetBB, inAppBB);668inNetBB.compact();669available = result.bytesProduced();670671if (available > 0)672inAppBB.flip();673else if (available == -1)674throw new IOException ("Stream is closed");675return available;676}677678/**679* block() only called when available==0 and buf is empty680*/681private synchronized void block () throws IOException {682//assert available == 0;683int n = selector.select ();684//assert n == 1;685selector.selectedKeys().clear();686available ();687}688689public void close () throws IOException {690if (closed)691return;692channel.close ();693closed = true;694}695696public synchronized void mark (int readlimit) {697if (closed)698return;699this.readlimit = readlimit;700markBuf = ByteBuffer.allocate (readlimit);701marked = true;702reset = false;703}704705public synchronized void reset () throws IOException {706if (closed )707return;708if (!marked)709throw new IOException ("Stream not marked");710marked = false;711reset = true;712markBuf.flip ();713}714}715716static class NioOutputStream extends OutputStream {717SSLEngine sslEng;718SocketChannel channel;719ByteBuffer outNetBB;720ByteBuffer outAppBB;721SelectionKey key;722Selector selector;723boolean closed;724byte[] one;725726public NioOutputStream (SocketChannel channel, SSLEngine sslEng, ByteBuffer outNetBB, ByteBuffer outAppBB) throws IOException {727this.sslEng = sslEng;728this.channel = channel;729this.outNetBB = outNetBB;730this.outAppBB = outAppBB;731selector = Selector.open ();732key = channel.register (selector, SelectionKey.OP_WRITE);733closed = false;734one = new byte [1];735}736737public synchronized void write (int b) throws IOException {738one[0] = (byte)b;739write (one, 0, 1);740}741742public synchronized void write (byte[] b) throws IOException {743write (b, 0, b.length);744}745746public synchronized void write (byte[] b, int off, int len) throws IOException {747if (closed)748throw new IOException ("stream is closed");749750outAppBB = ByteBuffer.allocate (len);751outAppBB.put (b, off, len);752outAppBB.flip ();753int n;754outNetBB.clear();755int needed = sslEng.getSession().getPacketBufferSize();756if (outNetBB.capacity() < needed) {757outNetBB = ByteBuffer.allocate(needed);758}759SSLEngineResult ret = sslEng.wrap(outAppBB, outNetBB);760outNetBB.flip();761int newLen = ret.bytesProduced();762while ((n = channel.write (outNetBB)) < newLen) {763newLen -= n;764if (newLen == 0)765return;766selector.select ();767selector.selectedKeys().clear ();768}769}770771public void close () throws IOException {772if (closed)773return;774channel.close ();775closed = true;776}777}778779/**780* Utilities for synchronization. A condition is781* identified by a string name, and is initialized782* upon first use (ie. setCondition() or waitForCondition()). Threads783* are blocked until some thread calls (or has called) setCondition() for the same784* condition.785* <P>786* A rendezvous built on a condition is also provided for synchronizing787* N threads.788*/789790private static HashMap conditions = new HashMap();791792/*793* Modifiable boolean object794*/795private static class BValue {796boolean v;797}798799/*800* Modifiable int object801*/802private static class IValue {803int v;804IValue (int i) {805v =i;806}807}808809810private static BValue getCond (String condition) {811synchronized (conditions) {812BValue cond = (BValue) conditions.get (condition);813if (cond == null) {814cond = new BValue();815conditions.put (condition, cond);816}817return cond;818}819}820821/**822* Set the condition to true. Any threads that are currently blocked823* waiting on the condition, will be unblocked and allowed to continue.824* Threads that subsequently call waitForCondition() will not block.825* If the named condition did not exist prior to the call, then it is created826* first.827*/828829public static void setCondition (String condition) {830BValue cond = getCond (condition);831synchronized (cond) {832if (cond.v) {833return;834}835cond.v = true;836cond.notifyAll();837}838}839840/**841* If the named condition does not exist, then it is created and initialized842* to false. If the condition exists or has just been created and its value843* is false, then the thread blocks until another thread sets the condition.844* If the condition exists and is already set to true, then this call returns845* immediately without blocking.846*/847848public static void waitForCondition (String condition) {849BValue cond = getCond (condition);850synchronized (cond) {851if (!cond.v) {852try {853cond.wait();854} catch (InterruptedException e) {}855}856}857}858859/* conditions must be locked when accessing this */860static HashMap rv = new HashMap();861862/**863* Force N threads to rendezvous (ie. wait for each other) before proceeding.864* The first thread(s) to call are blocked until the last865* thread makes the call. Then all threads continue.866* <p>867* All threads that call with the same condition name, must use the same value868* for N (or the results may be not be as expected).869* <P>870* Obviously, if fewer than N threads make the rendezvous then the result871* will be a hang.872*/873874public static void rendezvous (String condition, int N) {875BValue cond;876IValue iv;877String name = "RV_"+condition;878879/* get the condition */880881synchronized (conditions) {882cond = (BValue)conditions.get (name);883if (cond == null) {884/* we are first caller */885if (N < 2) {886throw new RuntimeException ("rendezvous must be called with N >= 2");887}888cond = new BValue ();889conditions.put (name, cond);890iv = new IValue (N-1);891rv.put (name, iv);892} else {893/* already initialised, just decrement the counter */894iv = (IValue) rv.get (name);895iv.v --;896}897}898899if (iv.v > 0) {900waitForCondition (name);901} else {902setCondition (name);903synchronized (conditions) {904clearCondition (name);905rv.remove (name);906}907}908}909910/**911* If the named condition exists and is set then remove it, so it can912* be re-initialized and used again. If the condition does not exist, or913* exists but is not set, then the call returns without doing anything.914* Note, some higher level synchronization915* may be needed between clear and the other operations.916*/917918public static void clearCondition(String condition) {919BValue cond;920synchronized (conditions) {921cond = (BValue) conditions.get (condition);922if (cond == null) {923return;924}925synchronized (cond) {926if (cond.v) {927conditions.remove (condition);928}929}930}931}932}933934935