/* * JLibs: Common Utilities for Java * Copyright (C) 2009 Santhosh Kumar T <santhosh.tekuri@gmail.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package jlibs.nio.http; import jlibs.core.lang.NotImplementedException; import jlibs.nio.*; import jlibs.nio.filters.InputLimitExceeded; import jlibs.nio.filters.ReadTrackingInput; import jlibs.nio.filters.TrackingInput; import jlibs.nio.http.expr.UnresolvedException; import jlibs.nio.http.msg.*; import jlibs.nio.http.msg.parser.RequestParser; import jlibs.nio.http.util.Expect; import jlibs.nio.http.util.USAscii; import java.io.IOException; import java.net.InetAddress; import java.net.SocketException; import java.net.SocketTimeoutException; import java.nio.ByteBuffer; import java.util.Collection; import java.util.Iterator; import java.util.List; import static java.nio.channels.SelectionKey.OP_READ; import static jlibs.nio.Debugger.HTTP; import static jlibs.nio.Debugger.println; import static jlibs.nio.http.ServerExchange.State.*; import static jlibs.nio.http.msg.Message.CONNECTION; import static jlibs.nio.http.msg.Message.PROXY_CONNECTION; import static jlibs.nio.http.msg.Method.CONNECT; /** * @author Santhosh Kumar Tekuri */ public final class ServerExchange extends Exchange{ private static final ByteBuffer CONTINUE_100; static{ Response response = new Response(); response.status = Status.CONTINUE; String string = response.toString(); CONTINUE_100 = ByteBuffer.allocateDirect(string.length()); USAscii.append(CONTINUE_100, string); CONTINUE_100.flip(); } private final HTTPServer server; private final RequestListener user; private Collection<ServerFilter> requestFilters; private Collection<ServerFilter> responseFilters; private Collection<ServerFilter> errorFilters; AccessLog accessLog; AccessLog.Record accessLogRecord; protected ServerExchange(HTTPServer server){ super(server.maxRequestHeadSize, new RequestParser(server.maxURISize), OP_READ); this.server = server; user = server.listener; requestFilters = server.requestFilters; responseFilters = server.responseFilters; errorFilters = server.errorFilters; accessLog = server.accessLog; if(accessLog!=null){ accessLogRecord = accessLog.records.allocate(); accessLogRecord.setLogHandler(server.logHandler); } connectionStatus = ConnectionStatus.OPEN; } enum State{ READ_REQUEST, FILTER_REQUEST, RESPONSE_READY, FILTER_RESPONSE, FILTER_ERROR, DELIVER_RESPONSE, DRAIN_REQUEST, WRITE_RESPONSE, CLOSED } private State state = READ_REQUEST; private boolean hasProxyConnectionHeader; private Version requestVersion; private boolean requestHasPayload; private ByteBuffer continue100Buffer; protected Iterator<ServerFilter> filters; @Override protected boolean process(int readyOp) throws IOException{ if(state==CLOSED) return true; if(continue100Buffer!=null){ try{ if(send(continue100Buffer)){ continue100Buffer = null; return false; } }catch(Throwable thr){ setError(thr); } } while(true){ try{ switch(state){ case READ_REQUEST: request = new Request(); readMessage.reset(request, false); setChild(readMessage); return true; case FILTER_REQUEST: while(response==null && filters.hasNext()){ if(!filters.next().filter(this, FilterType.REQUEST)) return false; } state = RESPONSE_READY; if(HTTP) println("state = "+state); if(response==null && !user.process(this)) return false; case RESPONSE_READY: filters = responseFilters.iterator(); state = FILTER_RESPONSE; if(HTTP) println("state = "+state); case FILTER_RESPONSE: while(filters.hasNext()){ if(!filters.next().filter(this, FilterType.RESPONSE)) return false; } state = DELIVER_RESPONSE; if(HTTP) println("state = "+state); case FILTER_ERROR: while(filters.hasNext()){ if(!filters.next().filter(this, FilterType.ERROR)) return false; } state = DELIVER_RESPONSE; if(HTTP) println("state = "+state); case DELIVER_RESPONSE: in = ((jlibs.nio.Readable)in.channel()).in(); in.setInputListener(listener); out.setOutputListener(listener); state = WRITE_RESPONSE; if(keepAlive && requestHasPayload){ if(in.eof()) state = DRAIN_REQUEST; else keepAlive = false; } if(HTTP) println("state = "+state); break; case DRAIN_REQUEST: if(!drainInputs()) return false; state = WRITE_RESPONSE; if(HTTP) println("state = "+state); case WRITE_RESPONSE: if(response==null){ if(error==null) error = Status.INTERNAL_SERVER_ERROR.with("Missing Response"); Status errorStatus; if(error instanceof Status) errorStatus = (Status)error; else errorStatus = Status.INTERNAL_SERVER_ERROR.with(error); response = new Response(); response.status = errorStatus; if(errorStatus.getCause()!=null) response.setPayload(new ErrorPayload(errorStatus.getCause())); } if(error!=null && Status.INTERNAL_SERVER_ERROR.equals(response.status)) Reactor.current().handleException(error); error = null; response.version = requestVersion; response.setKeepAlive(keepAlive); if(hasProxyConnectionHeader){ Header header = response.headers.remove(CONNECTION); if(header!=null) response.headers.set(PROXY_CONNECTION, header.getValue()); } if(server.setDateHeader) response.setDate(false); if(server.serverName !=null) response.setServer(server.serverName); writeMessage.reset(response, continue100Buffer, true); if(accessLog!=null) accessLogRecord.process(this, response); continue100Buffer = null; setChild(writeMessage); return true; case CLOSED: return true; } }catch(Throwable thr){ setError(thr); } } } @Override protected void reset(){ super.reset(); in.channel().taskCompleted(); state = READ_REQUEST; if(HTTP) println("state = "+state); hasProxyConnectionHeader = false; requestVersion = null; requestHasPayload = false; continue100Buffer = null; filters = null; callback = null; if(accessLog!=null){ accessLogRecord = accessLog.records.allocate(); accessLogRecord.setLogHandler(server.logHandler); } } @Override protected void readMessageFinished(Throwable thr){ if(accessLog!=null){ try{ accessLogRecord.process(this, request); }catch(Throwable thr1){ Reactor.current().handleException(thr1); } } if(thr!=null){ if(thr==ReadMessage.IGNORABLE_EOF_EXCEPTION){ if(accessLog!=null){ accessLogRecord.reset(); accessLog.records.free(accessLogRecord); } close(); return; } if(thr instanceof Status) error = thr; else if(thr instanceof SocketException){ error = thr; if(HTTP) println("error = "+error); notifyCallback(); close(); return; }else if(thr instanceof SocketTimeoutException) error = Status.REQUEST_TIMEOUT; else if(thr instanceof NotImplementedException) error = Status.NOT_IMPLEMENTED.with(thr); else error = Status.INTERNAL_SERVER_ERROR.with(thr); if(HTTP) println("error = "+error); } if(server.supportsProxyConnectionHeader){ Header header = request.headers.remove(PROXY_CONNECTION); if(header!=null){ hasProxyConnectionHeader = true; request.headers.set(CONNECTION, header.getValue()); } } keepAlive = error==null && (request.method==CONNECT || request.isKeepAlive()); requestVersion = request.version; requestHasPayload = request.getPayload().getContentLength()!=0; if(error==null && requestHasPayload && requestVersion.expectSupported){ Expect expectation = request.getExpectation(); if(expectation==Expect.CONTINUE_100){ SocketPayload socketPayload = (SocketPayload)request.getPayload(); in = socketPayload.in = new ReadTrackingInput(in, this::send100Continue); } else if(expectation!=null) error = Status.EXPECTATION_FAILED; } in.setInputListener(null); out.setOutputListener(null); if(error==null){ filters = requestFilters.iterator(); state = FILTER_REQUEST; }else{ filters = errorFilters.iterator(); state = FILTER_ERROR; } if(HTTP) println("state = "+state); } private void send100Continue(TrackingInput tracker){ continue100Buffer = CONTINUE_100.duplicate(); try{ if(send(continue100Buffer)) continue100Buffer = null; }catch(Throwable thr){ error = thr; close(); notifyCallback(); } } @Override protected void writeMessageFinished(Throwable thr){ error = thr; if(error!=null || !keepAlive) close(); notifyCallback(); if(in!=null) reset(); } private void clearResponse(){ if(response!=null){ if(response.getPayload() instanceof SocketPayload){ SocketPayload payload = (SocketPayload)response.getPayload(); if(payload.in!=null && payload.in.isOpen()){ try{ payload.in.close(); }catch(Throwable ignore){ Reactor.current().handleException(ignore); } } } response = null; } } protected void setError(Throwable thr){ if(thr instanceof SocketTimeoutException) error = Status.REQUEST_TIMEOUT; else if(thr instanceof InputLimitExceeded) error = Status.REQUEST_ENTITY_TOO_LARGE; else if(thr instanceof NotImplementedException) error = Status.NOT_IMPLEMENTED.with(thr); else error = thr; if(HTTP) println("error = "+error); if(state.ordinal()<FILTER_ERROR.ordinal()){ if(state==FILTER_RESPONSE) clearResponse(); filters = errorFilters.iterator(); state = FILTER_ERROR; if(HTTP) println("state = "+state); }else if(state==FILTER_ERROR){ clearResponse(); state = DELIVER_RESPONSE; if(HTTP) println("state = "+state); }else{ clearResponse(); notifyCallback(); close(); } } public void setResponse(Response response){ this.response = response; } protected ServerCallback callback; public void setCallback(ServerCallback callback){ this.callback = callback; } @SuppressWarnings("unchecked") @Trace(condition=HTTP) private void notifyCallback(){ try{ if(accessLog!=null) accessLogRecord.finished(this); }catch(Throwable thr){ Reactor.current().handleException(thr); } if(callback!=null){ try{ callback.completed(this, error); }catch(Throwable unexpected){ Reactor.current().handleException(unexpected); } callback = null; }else if(error!=null) Reactor.current().handleException(error); } @Override public void close(){ super.close(); state = CLOSED; if(HTTP) println("state = "+state); } @Override public TCPEndpoint getEndpoint(){ return server.endpoint; } public InetAddress getClientAddress(){ TCPConnection con = (TCPConnection)in.channel(); return con.selectable.socket().getInetAddress(); } @Override public Connection stealConnection(){ if(HTTP) println("stealConnection()"); Connection con = (Connection)in.channel(); in = null; out = null; state = CLOSED; return con; } @Override public String toString(){ String str = super.toString(); return str.substring(0, str.length()-1)+":"+state+"]"; } @Override @SuppressWarnings("StringEquality") public Object getField(String name) throws UnresolvedException{ if(name=="remote_ip"){ InetAddress address = getClientAddress(); return address.getHostAddress(); }else if(name=="client_ip"){ List<String> list = getRequest().getXForwardedFor(); if(!list.isEmpty()) return list.get(0); return getClientAddress().getHostAddress(); }else return super.getField(name); } }