Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/sun/net/httpserver/ServerImpl.java
38918 views
/*1* Copyright (c) 2005, 2013, 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. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.net.httpserver;2627import java.net.*;28import java.io.*;29import java.nio.channels.*;30import java.util.*;31import java.util.concurrent.*;32import java.util.logging.Logger;33import java.util.logging.Level;34import javax.net.ssl.*;35import com.sun.net.httpserver.*;36import java.security.AccessController;37import java.security.PrivilegedAction;38import sun.net.httpserver.HttpConnection.State;3940/**41* Provides implementation for both HTTP and HTTPS42*/43class ServerImpl implements TimeSource {4445private String protocol;46private boolean https;47private Executor executor;48private HttpsConfigurator httpsConfig;49private SSLContext sslContext;50private ContextList contexts;51private InetSocketAddress address;52private ServerSocketChannel schan;53private Selector selector;54private SelectionKey listenerKey;55private Set<HttpConnection> idleConnections;56private Set<HttpConnection> allConnections;57/* following two are used to keep track of the times58* when a connection/request is first received59* and when we start to send the response60*/61private Set<HttpConnection> reqConnections;62private Set<HttpConnection> rspConnections;63private List<Event> events;64private Object lolock = new Object();65private volatile boolean finished = false;66private volatile boolean terminating = false;67private boolean bound = false;68private boolean started = false;69private volatile long time; /* current time */70private volatile long subticks = 0;71private volatile long ticks; /* number of clock ticks since server started */72private HttpServer wrapper;7374final static int CLOCK_TICK = ServerConfig.getClockTick();75final static long IDLE_INTERVAL = ServerConfig.getIdleInterval();76final static int MAX_IDLE_CONNECTIONS = ServerConfig.getMaxIdleConnections();77final static long TIMER_MILLIS = ServerConfig.getTimerMillis ();78final static long MAX_REQ_TIME=getTimeMillis(ServerConfig.getMaxReqTime());79final static long MAX_RSP_TIME=getTimeMillis(ServerConfig.getMaxRspTime());80final static boolean timer1Enabled = MAX_REQ_TIME != -1 || MAX_RSP_TIME != -1;8182private Timer timer, timer1;83private Logger logger;8485ServerImpl (86HttpServer wrapper, String protocol, InetSocketAddress addr, int backlog87) throws IOException {8889this.protocol = protocol;90this.wrapper = wrapper;91this.logger = Logger.getLogger ("com.sun.net.httpserver");92ServerConfig.checkLegacyProperties (logger);93https = protocol.equalsIgnoreCase ("https");94this.address = addr;95contexts = new ContextList();96schan = ServerSocketChannel.open();97if (addr != null) {98ServerSocket socket = schan.socket();99socket.bind (addr, backlog);100bound = true;101}102selector = Selector.open ();103schan.configureBlocking (false);104listenerKey = schan.register (selector, SelectionKey.OP_ACCEPT);105dispatcher = new Dispatcher();106idleConnections = Collections.synchronizedSet (new HashSet<HttpConnection>());107allConnections = Collections.synchronizedSet (new HashSet<HttpConnection>());108reqConnections = Collections.synchronizedSet (new HashSet<HttpConnection>());109rspConnections = Collections.synchronizedSet (new HashSet<HttpConnection>());110time = System.currentTimeMillis();111timer = new Timer ("server-timer", true);112timer.schedule (new ServerTimerTask(), CLOCK_TICK, CLOCK_TICK);113if (timer1Enabled) {114timer1 = new Timer ("server-timer1", true);115timer1.schedule (new ServerTimerTask1(),TIMER_MILLIS,TIMER_MILLIS);116logger.config ("HttpServer timer1 enabled period in ms: "+TIMER_MILLIS);117logger.config ("MAX_REQ_TIME: "+MAX_REQ_TIME);118logger.config ("MAX_RSP_TIME: "+MAX_RSP_TIME);119}120events = new LinkedList<Event>();121logger.config ("HttpServer created "+protocol+" "+ addr);122}123124public void bind (InetSocketAddress addr, int backlog) throws IOException {125if (bound) {126throw new BindException ("HttpServer already bound");127}128if (addr == null) {129throw new NullPointerException ("null address");130}131ServerSocket socket = schan.socket();132socket.bind (addr, backlog);133bound = true;134}135136public void start () {137if (!bound || started || finished) {138throw new IllegalStateException ("server in wrong state");139}140if (executor == null) {141executor = new DefaultExecutor();142}143Thread t = new Thread (dispatcher);144started = true;145t.start();146}147148public void setExecutor (Executor executor) {149if (started) {150throw new IllegalStateException ("server already started");151}152this.executor = executor;153}154155private static class DefaultExecutor implements Executor {156public void execute (Runnable task) {157task.run();158}159}160161public Executor getExecutor () {162return executor;163}164165public void setHttpsConfigurator (HttpsConfigurator config) {166if (config == null) {167throw new NullPointerException ("null HttpsConfigurator");168}169if (started) {170throw new IllegalStateException ("server already started");171}172this.httpsConfig = config;173sslContext = config.getSSLContext();174}175176public HttpsConfigurator getHttpsConfigurator () {177return httpsConfig;178}179180public void stop (int delay) {181if (delay < 0) {182throw new IllegalArgumentException ("negative delay parameter");183}184terminating = true;185try { schan.close(); } catch (IOException e) {}186selector.wakeup();187long latest = System.currentTimeMillis() + delay * 1000;188while (System.currentTimeMillis() < latest) {189delay();190if (finished) {191break;192}193}194finished = true;195selector.wakeup();196synchronized (allConnections) {197for (HttpConnection c : allConnections) {198c.close();199}200}201allConnections.clear();202idleConnections.clear();203timer.cancel();204if (timer1Enabled) {205timer1.cancel();206}207}208209Dispatcher dispatcher;210211public synchronized HttpContextImpl createContext (String path, HttpHandler handler) {212if (handler == null || path == null) {213throw new NullPointerException ("null handler, or path parameter");214}215HttpContextImpl context = new HttpContextImpl (protocol, path, handler, this);216contexts.add (context);217logger.config ("context created: " + path);218return context;219}220221public synchronized HttpContextImpl createContext (String path) {222if (path == null) {223throw new NullPointerException ("null path parameter");224}225HttpContextImpl context = new HttpContextImpl (protocol, path, null, this);226contexts.add (context);227logger.config ("context created: " + path);228return context;229}230231public synchronized void removeContext (String path) throws IllegalArgumentException {232if (path == null) {233throw new NullPointerException ("null path parameter");234}235contexts.remove (protocol, path);236logger.config ("context removed: " + path);237}238239public synchronized void removeContext (HttpContext context) throws IllegalArgumentException {240if (!(context instanceof HttpContextImpl)) {241throw new IllegalArgumentException ("wrong HttpContext type");242}243contexts.remove ((HttpContextImpl)context);244logger.config ("context removed: " + context.getPath());245}246247public InetSocketAddress getAddress() {248return AccessController.doPrivileged(249new PrivilegedAction<InetSocketAddress>() {250public InetSocketAddress run() {251return252(InetSocketAddress)schan.socket()253.getLocalSocketAddress();254}255});256}257258Selector getSelector () {259return selector;260}261262void addEvent (Event r) {263synchronized (lolock) {264events.add (r);265selector.wakeup();266}267}268269/* main server listener task */270271class Dispatcher implements Runnable {272273private void handleEvent (Event r) {274ExchangeImpl t = r.exchange;275HttpConnection c = t.getConnection();276try {277if (r instanceof WriteFinishedEvent) {278279int exchanges = endExchange();280if (terminating && exchanges == 0) {281finished = true;282}283responseCompleted (c);284LeftOverInputStream is = t.getOriginalInputStream();285if (!is.isEOF()) {286t.close = true;287}288if (t.close || idleConnections.size() >= MAX_IDLE_CONNECTIONS) {289c.close();290allConnections.remove (c);291} else {292if (is.isDataBuffered()) {293/* don't re-enable the interestops, just handle it */294requestStarted (c);295handle (c.getChannel(), c);296} else {297connsToRegister.add (c);298}299}300}301} catch (IOException e) {302logger.log (303Level.FINER, "Dispatcher (1)", e304);305c.close();306}307}308309final LinkedList<HttpConnection> connsToRegister =310new LinkedList<HttpConnection>();311312void reRegister (HttpConnection c) {313/* re-register with selector */314try {315SocketChannel chan = c.getChannel();316chan.configureBlocking (false);317SelectionKey key = chan.register (selector, SelectionKey.OP_READ);318key.attach (c);319c.selectionKey = key;320c.time = getTime() + IDLE_INTERVAL;321idleConnections.add (c);322} catch (IOException e) {323dprint(e);324logger.log(Level.FINER, "Dispatcher(8)", e);325c.close();326}327}328329public void run() {330while (!finished) {331try {332List<Event> list = null;333synchronized (lolock) {334if (events.size() > 0) {335list = events;336events = new LinkedList<Event>();337}338}339340if (list != null) {341for (Event r: list) {342handleEvent (r);343}344}345346for (HttpConnection c : connsToRegister) {347reRegister(c);348}349connsToRegister.clear();350351selector.select(1000);352353/* process the selected list now */354Set<SelectionKey> selected = selector.selectedKeys();355Iterator<SelectionKey> iter = selected.iterator();356while (iter.hasNext()) {357SelectionKey key = iter.next();358iter.remove ();359if (key.equals (listenerKey)) {360if (terminating) {361continue;362}363SocketChannel chan = schan.accept();364365// Set TCP_NODELAY, if appropriate366if (ServerConfig.noDelay()) {367chan.socket().setTcpNoDelay(true);368}369370if (chan == null) {371continue; /* cancel something ? */372}373chan.configureBlocking (false);374SelectionKey newkey = chan.register (selector, SelectionKey.OP_READ);375HttpConnection c = new HttpConnection ();376c.selectionKey = newkey;377c.setChannel (chan);378newkey.attach (c);379requestStarted (c);380allConnections.add (c);381} else {382try {383if (key.isReadable()) {384boolean closed;385SocketChannel chan = (SocketChannel)key.channel();386HttpConnection conn = (HttpConnection)key.attachment();387388key.cancel();389chan.configureBlocking (true);390if (idleConnections.remove(conn)) {391// was an idle connection so add it392// to reqConnections set.393requestStarted (conn);394}395handle (chan, conn);396} else {397assert false;398}399} catch (CancelledKeyException e) {400handleException(key, null);401} catch (IOException e) {402handleException(key, e);403}404}405}406// call the selector just to process the cancelled keys407selector.selectNow();408} catch (IOException e) {409logger.log (Level.FINER, "Dispatcher (4)", e);410} catch (Exception e) {411logger.log (Level.FINER, "Dispatcher (7)", e);412}413}414try {selector.close(); } catch (Exception e) {}415}416417private void handleException (SelectionKey key, Exception e) {418HttpConnection conn = (HttpConnection)key.attachment();419if (e != null) {420logger.log (Level.FINER, "Dispatcher (2)", e);421}422closeConnection(conn);423}424425public void handle (SocketChannel chan, HttpConnection conn)426throws IOException427{428try {429Exchange t = new Exchange (chan, protocol, conn);430executor.execute (t);431} catch (HttpError e1) {432logger.log (Level.FINER, "Dispatcher (4)", e1);433closeConnection(conn);434} catch (IOException e) {435logger.log (Level.FINER, "Dispatcher (5)", e);436closeConnection(conn);437}438}439}440441static boolean debug = ServerConfig.debugEnabled ();442443static synchronized void dprint (String s) {444if (debug) {445System.out.println (s);446}447}448449static synchronized void dprint (Exception e) {450if (debug) {451System.out.println (e);452e.printStackTrace();453}454}455456Logger getLogger () {457return logger;458}459460private void closeConnection(HttpConnection conn) {461conn.close();462allConnections.remove(conn);463switch (conn.getState()) {464case REQUEST:465reqConnections.remove(conn);466break;467case RESPONSE:468rspConnections.remove(conn);469break;470case IDLE:471idleConnections.remove(conn);472break;473}474assert !reqConnections.remove(conn);475assert !rspConnections.remove(conn);476assert !idleConnections.remove(conn);477}478479/* per exchange task */480481class Exchange implements Runnable {482SocketChannel chan;483HttpConnection connection;484HttpContextImpl context;485InputStream rawin;486OutputStream rawout;487String protocol;488ExchangeImpl tx;489HttpContextImpl ctx;490boolean rejected = false;491492Exchange (SocketChannel chan, String protocol, HttpConnection conn) throws IOException {493this.chan = chan;494this.connection = conn;495this.protocol = protocol;496}497498public void run () {499/* context will be null for new connections */500context = connection.getHttpContext();501boolean newconnection;502SSLEngine engine = null;503String requestLine = null;504SSLStreams sslStreams = null;505try {506if (context != null ) {507this.rawin = connection.getInputStream();508this.rawout = connection.getRawOutputStream();509newconnection = false;510} else {511/* figure out what kind of connection this is */512newconnection = true;513if (https) {514if (sslContext == null) {515logger.warning ("SSL connection received. No https contxt created");516throw new HttpError ("No SSL context established");517}518sslStreams = new SSLStreams (ServerImpl.this, sslContext, chan);519rawin = sslStreams.getInputStream();520rawout = sslStreams.getOutputStream();521engine = sslStreams.getSSLEngine();522connection.sslStreams = sslStreams;523} else {524rawin = new BufferedInputStream(525new Request.ReadStream (526ServerImpl.this, chan527));528rawout = new Request.WriteStream (529ServerImpl.this, chan530);531}532connection.raw = rawin;533connection.rawout = rawout;534}535Request req = new Request (rawin, rawout);536requestLine = req.requestLine();537if (requestLine == null) {538/* connection closed */539closeConnection(connection);540return;541}542int space = requestLine.indexOf (' ');543if (space == -1) {544reject (Code.HTTP_BAD_REQUEST,545requestLine, "Bad request line");546return;547}548String method = requestLine.substring (0, space);549int start = space+1;550space = requestLine.indexOf(' ', start);551if (space == -1) {552reject (Code.HTTP_BAD_REQUEST,553requestLine, "Bad request line");554return;555}556String uriStr = requestLine.substring (start, space);557URI uri = new URI (uriStr);558start = space+1;559String version = requestLine.substring (start);560Headers headers = req.headers();561String s = headers.getFirst ("Transfer-encoding");562long clen = 0L;563if (s !=null && s.equalsIgnoreCase ("chunked")) {564clen = -1L;565} else {566s = headers.getFirst ("Content-Length");567if (s != null) {568clen = Long.parseLong(s);569}570if (clen == 0) {571requestCompleted (connection);572}573}574ctx = contexts.findContext (protocol, uri.getPath());575if (ctx == null) {576reject (Code.HTTP_NOT_FOUND,577requestLine, "No context found for request");578return;579}580connection.setContext (ctx);581if (ctx.getHandler() == null) {582reject (Code.HTTP_INTERNAL_ERROR,583requestLine, "No handler for context");584return;585}586tx = new ExchangeImpl (587method, uri, req, clen, connection588);589String chdr = headers.getFirst("Connection");590Headers rheaders = tx.getResponseHeaders();591592if (chdr != null && chdr.equalsIgnoreCase ("close")) {593tx.close = true;594}595if (version.equalsIgnoreCase ("http/1.0")) {596tx.http10 = true;597if (chdr == null) {598tx.close = true;599rheaders.set ("Connection", "close");600} else if (chdr.equalsIgnoreCase ("keep-alive")) {601rheaders.set ("Connection", "keep-alive");602int idle=(int)(ServerConfig.getIdleInterval()/1000);603int max=ServerConfig.getMaxIdleConnections();604String val = "timeout="+idle+", max="+max;605rheaders.set ("Keep-Alive", val);606}607}608609if (newconnection) {610connection.setParameters (611rawin, rawout, chan, engine, sslStreams,612sslContext, protocol, ctx, rawin613);614}615/* check if client sent an Expect 100 Continue.616* In that case, need to send an interim response.617* In future API may be modified to allow app to618* be involved in this process.619*/620String exp = headers.getFirst("Expect");621if (exp != null && exp.equalsIgnoreCase ("100-continue")) {622logReply (100, requestLine, null);623sendReply (624Code.HTTP_CONTINUE, false, null625);626}627/* uf is the list of filters seen/set by the user.628* sf is the list of filters established internally629* and which are not visible to the user. uc and sc630* are the corresponding Filter.Chains.631* They are linked together by a LinkHandler632* so that they can both be invoked in one call.633*/634List<Filter> sf = ctx.getSystemFilters();635List<Filter> uf = ctx.getFilters();636637Filter.Chain sc = new Filter.Chain(sf, ctx.getHandler());638Filter.Chain uc = new Filter.Chain(uf, new LinkHandler (sc));639640/* set up the two stream references */641tx.getRequestBody();642tx.getResponseBody();643if (https) {644uc.doFilter (new HttpsExchangeImpl (tx));645} else {646uc.doFilter (new HttpExchangeImpl (tx));647}648649} catch (IOException e1) {650logger.log (Level.FINER, "ServerImpl.Exchange (1)", e1);651closeConnection(connection);652} catch (NumberFormatException e3) {653reject (Code.HTTP_BAD_REQUEST,654requestLine, "NumberFormatException thrown");655} catch (URISyntaxException e) {656reject (Code.HTTP_BAD_REQUEST,657requestLine, "URISyntaxException thrown");658} catch (Exception e4) {659logger.log (Level.FINER, "ServerImpl.Exchange (2)", e4);660closeConnection(connection);661}662}663664/* used to link to 2 or more Filter.Chains together */665666class LinkHandler implements HttpHandler {667Filter.Chain nextChain;668669LinkHandler (Filter.Chain nextChain) {670this.nextChain = nextChain;671}672673public void handle (HttpExchange exchange) throws IOException {674nextChain.doFilter (exchange);675}676}677678void reject (int code, String requestStr, String message) {679rejected = true;680logReply (code, requestStr, message);681sendReply (682code, false, "<h1>"+code+Code.msg(code)+"</h1>"+message683);684closeConnection(connection);685}686687void sendReply (688int code, boolean closeNow, String text)689{690try {691StringBuilder builder = new StringBuilder (512);692builder.append ("HTTP/1.1 ")693.append (code).append (Code.msg(code)).append ("\r\n");694695if (text != null && text.length() != 0) {696builder.append ("Content-Length: ")697.append (text.length()).append ("\r\n")698.append ("Content-Type: text/html\r\n");699} else {700builder.append ("Content-Length: 0\r\n");701text = "";702}703if (closeNow) {704builder.append ("Connection: close\r\n");705}706builder.append ("\r\n").append (text);707String s = builder.toString();708byte[] b = s.getBytes("ISO8859_1");709rawout.write (b);710rawout.flush();711if (closeNow) {712closeConnection(connection);713}714} catch (IOException e) {715logger.log (Level.FINER, "ServerImpl.sendReply", e);716closeConnection(connection);717}718}719720}721722void logReply (int code, String requestStr, String text) {723if (!logger.isLoggable(Level.FINE)) {724return;725}726if (text == null) {727text = "";728}729String r;730if (requestStr.length() > 80) {731r = requestStr.substring (0, 80) + "<TRUNCATED>";732} else {733r = requestStr;734}735String message = r + " [" + code + " " +736Code.msg(code) + "] ("+text+")";737logger.fine (message);738}739740long getTicks() {741return ticks;742}743744public long getTime() {745return time;746}747748void delay () {749Thread.yield();750try {751Thread.sleep (200);752} catch (InterruptedException e) {}753}754755private int exchangeCount = 0;756757synchronized void startExchange () {758exchangeCount ++;759}760761synchronized int endExchange () {762exchangeCount --;763assert exchangeCount >= 0;764return exchangeCount;765}766767HttpServer getWrapper () {768return wrapper;769}770771void requestStarted (HttpConnection c) {772c.creationTime = getTime();773c.setState (State.REQUEST);774reqConnections.add (c);775}776777// called after a request has been completely read778// by the server. This stops the timer which would779// close the connection if the request doesn't arrive780// quickly enough. It then starts the timer781// that ensures the client reads the response in a timely782// fashion.783784void requestCompleted (HttpConnection c) {785assert c.getState() == State.REQUEST;786reqConnections.remove (c);787c.rspStartedTime = getTime();788rspConnections.add (c);789c.setState (State.RESPONSE);790}791792// called after response has been sent793void responseCompleted (HttpConnection c) {794assert c.getState() == State.RESPONSE;795rspConnections.remove (c);796c.setState (State.IDLE);797}798799/**800* TimerTask run every CLOCK_TICK ms801*/802class ServerTimerTask extends TimerTask {803public void run () {804LinkedList<HttpConnection> toClose = new LinkedList<HttpConnection>();805time = System.currentTimeMillis();806ticks ++;807synchronized (idleConnections) {808for (HttpConnection c : idleConnections) {809if (c.time <= time) {810toClose.add (c);811}812}813for (HttpConnection c : toClose) {814idleConnections.remove (c);815allConnections.remove (c);816c.close();817}818}819}820}821822class ServerTimerTask1 extends TimerTask {823824// runs every TIMER_MILLIS825public void run () {826LinkedList<HttpConnection> toClose = new LinkedList<HttpConnection>();827time = System.currentTimeMillis();828synchronized (reqConnections) {829if (MAX_REQ_TIME != -1) {830for (HttpConnection c : reqConnections) {831if (c.creationTime + TIMER_MILLIS + MAX_REQ_TIME <= time) {832toClose.add (c);833}834}835for (HttpConnection c : toClose) {836logger.log (Level.FINE, "closing: no request: " + c);837reqConnections.remove (c);838allConnections.remove (c);839c.close();840}841}842}843toClose = new LinkedList<HttpConnection>();844synchronized (rspConnections) {845if (MAX_RSP_TIME != -1) {846for (HttpConnection c : rspConnections) {847if (c.rspStartedTime + TIMER_MILLIS +MAX_RSP_TIME <= time) {848toClose.add (c);849}850}851for (HttpConnection c : toClose) {852logger.log (Level.FINE, "closing: no response: " + c);853rspConnections.remove (c);854allConnections.remove (c);855c.close();856}857}858}859}860}861862void logStackTrace (String s) {863logger.finest (s);864StringBuilder b = new StringBuilder ();865StackTraceElement[] e = Thread.currentThread().getStackTrace();866for (int i=0; i<e.length; i++) {867b.append (e[i].toString()).append("\n");868}869logger.finest (b.toString());870}871872static long getTimeMillis(long secs) {873if (secs == -1) {874return -1;875} else {876return secs * 1000;877}878}879}880881882