/** * ConsoleNotifier * * <p>Provides a way to output notification messages to a log file */ public class LoggingNotifier implements EventNotifier { private static final Logger LOG = Log.getLogger(LoggingNotifier.class); String _messageFormat; /* ------------------------------------------------------------ */ /** * Constructs a new notifier with specified format string * * @param format the {@link java.util.Formatter format string} * @throws IllegalArgumentException */ public LoggingNotifier(String format) throws IllegalArgumentException { if (format == null) throw new IllegalArgumentException("Message format cannot be null"); _messageFormat = format; } /* ------------------------------------------------------------ */ /** * @see * org.eclipse.jetty.monitor.jmx.EventNotifier#notify(org.eclipse.jetty.monitor.jmx.EventTrigger, * org.eclipse.jetty.monitor.jmx.EventState, long) */ public void notify(EventTrigger trigger, EventState<?> state, long timestamp) { String output = String.format(_messageFormat, state); LOG.info(output); } }
/** * Registers mbeans with the default platform MBean Server. * * @author David Ranalli Jan 29, 2016 */ public class JmxMonitorManager { private static final Logger log = Log.getLogger(JmxMonitorManager.class.getName()); private static final String BEAN_NAME = "jetty-nosql.%s:type=%s,name=%s"; public static void monitor(Object mbean, String category, String type, String name) { MBeanServer server = ManagementFactory.getPlatformMBeanServer(); try { server.registerMBean(mbean, new ObjectName(String.format(BEAN_NAME, category, type, name))); } catch (Exception e) { // Throwing runtime exception will not help matters... // silently fail and record the problem log.info( "Could not add mbean " + mbean + " as " + String.format(BEAN_NAME, category, type, name), e); } } public static void remove(String category, String type, String name) { MBeanServer server = ManagementFactory.getPlatformMBeanServer(); try { server.unregisterMBean(new ObjectName(String.format(BEAN_NAME, category, type, name))); } catch (Exception e) { // Throwing runtime exception will not help matters... // silently fail and record the problem log.info("Could not remove mbean " + String.format(BEAN_NAME, category, type, name), e); } } }
@ServerEndpoint("/echo/primitives/byteobject") public class ByteObjectTextSocket { private static final Logger LOG = Log.getLogger(ByteObjectTextSocket.class); private Session session; @OnOpen public void onOpen(Session session) { this.session = session; } @OnMessage public void onMessage(Byte b) throws IOException { if (b == null) { session.getAsyncRemote().sendText("Error: Byte is null"); } else { String msg = String.format("0x%02X", b); session.getAsyncRemote().sendText(msg); } } @OnError public void onError(Throwable cause) throws IOException { LOG.warn("Error", cause); session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause)); } }
@Before public void init() throws Exception { _server = new Server(); _connector = new LocalConnector(_server); ServletContextHandler context = new ServletContextHandler( ServletContextHandler.NO_SECURITY | ServletContextHandler.NO_SESSIONS); _server.addConnector(_connector); _server.setHandler(context); context.setContextPath("/"); context.addServlet(DefaultServlet.class, "/"); context.addServlet(FailServlet.class, "/fail/*"); context.addServlet(ErrorServlet.class, "/error/*"); ErrorPageErrorHandler error = new ErrorPageErrorHandler(); context.setErrorHandler(error); error.addErrorPage(599, "/error/599"); error.addErrorPage(IllegalStateException.class.getCanonicalName(), "/error/TestException"); error.addErrorPage(ErrorPageErrorHandler.GLOBAL_ERROR_PAGE, "/error/GlobalErrorPage"); _server.start(); ((StdErrLog) Log.getLogger(ServletHandler.class)).setHideStacks(true); }
@ServerEndpoint("/echo/primitives/characterobject") public class CharacterObjectTextSocket { private static final Logger LOG = Log.getLogger(CharacterObjectTextSocket.class); private Session session; @OnOpen public void onOpen(Session session) { this.session = session; } @OnMessage public void onMessage(Character c) throws IOException { if (c == null) { session.getAsyncRemote().sendText("Error: Character is null"); } else { String msg = c.toString(); session.getAsyncRemote().sendText(msg); } } @OnError public void onError(Throwable cause) throws IOException { LOG.warn("Error", cause); session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause)); } }
@Test public void testStop() throws Exception { TestLifeCycle lifecycle = new TestLifeCycle(); TestListener listener = new TestListener(); lifecycle.addLifeCycleListener(listener); // need to set the state to something other than stopped or stopping or // else // stop() will return without doing anything lifecycle.start(); lifecycle.setCause(cause); try { ((StdErrLog) Log.getLogger(AbstractLifeCycle.class)).setHideStacks(true); lifecycle.stop(); assertTrue(false); } catch (Exception e) { assertEquals(cause, e); assertEquals(cause, listener.getCause()); } finally { ((StdErrLog) Log.getLogger(AbstractLifeCycle.class)).setHideStacks(false); } lifecycle.setCause(null); lifecycle.stop(); // check that the stopping event has been thrown assertTrue("The stopping event didn't occur", listener.stopping); // check that the stopped event has been thrown assertTrue("The stopped event didn't occur", listener.stopped); // check that the stopping event occurs before the stopped event assertTrue( "The stopping event must occur before the stopped event", listener.stoppingTime <= listener.stoppedTime); // System.out.println("STOPING TIME : " + listener.stoppingTime + " : " + listener.stoppedTime); // check that the lifecycle's state is stopped assertTrue("The lifecycle state is not stooped", lifecycle.isStopped()); }
/** Dummy impl of MuxAddServer */ public class DummyMuxAddServer implements MuxAddServer { @SuppressWarnings("unused") private static final Logger LOG = Log.getLogger(DummyMuxAddServer.class); private AdapterEchoSocket echo; private WebSocketPolicy policy; private EventDriverFactory eventDriverFactory; public DummyMuxAddServer() { this.policy = WebSocketPolicy.newServerPolicy(); this.eventDriverFactory = new EventDriverFactory(policy); this.echo = new AdapterEchoSocket(); } @Override public UpgradeRequest getPhysicalHandshakeRequest() { // TODO Auto-generated method stub return null; } @Override public UpgradeResponse getPhysicalHandshakeResponse() { // TODO Auto-generated method stub return null; } @Override public void handshake(Muxer muxer, MuxChannel channel, UpgradeRequest request) throws MuxException, IOException { StringBuilder response = new StringBuilder(); response.append("HTTP/1.1 101 Switching Protocols\r\n"); response.append("Connection: upgrade\r\n"); // not meaningful (per Draft 08) hresp.append("Upgrade: websocket\r\n"); // not meaningful (per Draft 08) hresp.append("Sec-WebSocket-Accept: // Kgo85/8KVE8YPONSeyhgL3GwqhI=\r\n"); response.append("\r\n"); EventDriver websocket = this.eventDriverFactory.wrap(echo); WebSocketSession session = new WebSocketSession(request.getRequestURI(), websocket, channel); session.setNegotiatedSubprotocol("echo"); channel.setSession(session); channel.setSubProtocol("echo"); channel.onOpen(); session.open(); MuxAddChannelResponse addChannelResponse = new MuxAddChannelResponse(); addChannelResponse.setChannelId(channel.getChannelId()); addChannelResponse.setEncoding(MuxAddChannelResponse.IDENTITY_ENCODING); addChannelResponse.setFailed(false); addChannelResponse.setHandshake(response.toString()); muxer.output(addChannelResponse); } }
public class SpnegoAuthenticator extends LoginAuthenticator { private static final Logger LOG = Log.getLogger(SpnegoAuthenticator.class); public String getAuthMethod() { return Constraint.__SPNEGO_AUTH; } public Authentication validateRequest( ServletRequest request, ServletResponse response, boolean mandatory) throws ServerAuthException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse res = (HttpServletResponse) response; String header = req.getHeader(HttpHeaders.AUTHORIZATION); if (!mandatory) { return _deferred; } // check to see if we have authorization headers required to continue if (header == null) { try { if (_deferred.isDeferred(res)) { return Authentication.UNAUTHENTICATED; } LOG.debug("SpengoAuthenticator: sending challenge"); res.setHeader(HttpHeaders.WWW_AUTHENTICATE, HttpHeaders.NEGOTIATE); res.sendError(HttpServletResponse.SC_UNAUTHORIZED); return Authentication.SEND_CONTINUE; } catch (IOException ioe) { throw new ServerAuthException(ioe); } } else if (header != null && header.startsWith(HttpHeaders.NEGOTIATE)) { String spnegoToken = header.substring(10); UserIdentity user = _loginService.login(null, spnegoToken); if (user != null) { return new UserAuthentication(getAuthMethod(), user); } } return Authentication.UNAUTHENTICATED; } public boolean secureResponse( ServletRequest request, ServletResponse response, boolean mandatory, User validatedUser) throws ServerAuthException { return true; } }
/** On Connect, close socket */ public static class FastCloseSocket extends AbstractCloseSocket { private static final Logger LOG = Log.getLogger(ManyConnectionsCleanupTest.FastCloseSocket.class); private final AtomicInteger calls; public FastCloseSocket(AtomicInteger calls) { this.calls = calls; } @Override public void onWebSocketConnect(Session sess) { LOG.debug("onWebSocketConnect({})", sess); calls.incrementAndGet(); sess.close(StatusCode.NORMAL, "FastCloseServer"); } }
/** * {@link ProxyEngine} is the class for SPDY proxy functionalities that receives a SPDY request and * converts it to any protocol to its server side. * * <p>This class listens for SPDY events sent by clients; subclasses are responsible for translating * these SPDY client events into appropriate events to forward to the server, in the appropriate * protocol that is understood by the server. */ public abstract class ProxyEngine { protected final Logger logger = Log.getLogger(getClass()); private final String name; protected ProxyEngine() { this(name()); } private static String name() { try { return InetAddress.getLocalHost().getHostName(); } catch (UnknownHostException x) { return "localhost"; } } public abstract StreamFrameListener proxy( Stream clientStream, SynInfo clientSynInfo, ProxyEngineSelector.ProxyServerInfo proxyServerInfo); protected ProxyEngine(String name) { this.name = name; } public String getName() { return name; } protected void addRequestProxyHeaders(Stream stream, Fields headers) { addViaHeader(headers); InetSocketAddress address = stream.getSession().getRemoteAddress(); if (address != null) headers.add("X-Forwarded-For", address.getHostName()); } protected void addResponseProxyHeaders(Stream stream, Fields headers) { addViaHeader(headers); } private void addViaHeader(Fields headers) { headers.add("Via", "http/1.1 " + getName()); } protected void customizeRequestHeaders(Stream stream, Fields headers) {} protected void customizeResponseHeaders(Stream stream, Fields headers) {} }
/** Simple Echo WebSocket, using async writes of echo */ @WebSocket public class ABSocket { private static Logger LOG = Log.getLogger(ABSocket.class); private Session session; private String abbreviate(String message) { if (message.length() > 80) { return '"' + message.substring(0, 80) + "\"..."; } return '"' + message + '"'; } @OnWebSocketMessage public void onBinary(byte buf[], int offset, int len) { LOG.debug("onBinary(byte[{}],{},{})", buf.length, offset, len); // echo the message back. ByteBuffer data = ByteBuffer.wrap(buf, offset, len); this.session.getRemote().sendBytesByFuture(data); } @OnWebSocketConnect public void onOpen(Session sess) { this.session = sess; } @OnWebSocketMessage public void onText(String message) { if (LOG.isDebugEnabled()) { if (message == null) { LOG.debug("onText() msg=null"); } else { LOG.debug("onText() size={}, msg={}", message.length(), abbreviate(message)); } } try { // echo the message back. this.session.getRemote().sendStringByFuture(message); } catch (WebSocketException e) { LOG.warn("Unable to echo TEXT message", e); } } }
/** On Connect, throw unhandled exception */ public static class FastFailSocket extends AbstractCloseSocket { private static final Logger LOG = Log.getLogger(ManyConnectionsCleanupTest.FastFailSocket.class); private final AtomicInteger calls; public FastFailSocket(AtomicInteger calls) { this.calls = calls; } @Override public void onWebSocketConnect(Session sess) { LOG.debug("onWebSocketConnect({})", sess); calls.incrementAndGet(); // Test failure due to unhandled exception // this should trigger a fast-fail closure during open/connect throw new RuntimeException("Intentional FastFail"); } }
/** Producer of {@link org.eclipse.jetty.websocket.api.Session} instances */ public class JettyWebSocketSessionProducer { private static final Logger LOG = Log.getLogger(JettyWebSocketSessionProducer.class); @Produces public Session getSession(InjectionPoint injectionPoint) { if (LOG.isDebugEnabled()) { LOG.debug("getSession({})", injectionPoint); } WebSocketScopeContext ctx = WebSocketScopeContext.current(); if (ctx == null) { throw new IllegalStateException("Not in a " + WebSocketScope.class.getName()); } org.eclipse.jetty.websocket.api.Session sess = ctx.getSession(); if (sess == null) { throw new IllegalStateException("No Session Available"); } return sess; } }
/** * Convert an {@link Enum} to JSON. If fromJSON is true in the constructor, the JSON generated will * be of the form {class="com.acme.TrafficLight",value="Green"} If fromJSON is false, then only the * string value of the enum is generated. */ public class JSONEnumConvertor implements JSON.Convertor { private static final Logger LOG = Log.getLogger(JSONEnumConvertor.class); private boolean _fromJSON; private Method _valueOf; { try { Class<?> e = Loader.loadClass(getClass(), "java.lang.Enum"); _valueOf = e.getMethod("valueOf", Class.class, String.class); } catch (Exception e) { throw new RuntimeException("!Enums", e); } } public JSONEnumConvertor() { this(false); } public JSONEnumConvertor(boolean fromJSON) { _fromJSON = fromJSON; } public Object fromJSON(Map map) { if (!_fromJSON) throw new UnsupportedOperationException(); try { Class c = Loader.loadClass(getClass(), (String) map.get("class")); return _valueOf.invoke(null, c, map.get("value")); } catch (Exception e) { LOG.warn(e); } return null; } public void toJSON(Object obj, Output out) { if (_fromJSON) { out.addClass(obj.getClass()); out.add("value", ((Enum) obj).name()); } else { out.add(((Enum) obj).name()); } } }
/** On Message, return container information */ public static class ContainerSocket extends AbstractCloseSocket { private static final Logger LOG = Log.getLogger(ManyConnectionsCleanupTest.ContainerSocket.class); private final WebSocketServerFactory container; private final AtomicInteger calls; private Session session; public ContainerSocket(WebSocketServerFactory container, AtomicInteger calls) { this.container = container; this.calls = calls; } @Override public void onWebSocketText(String message) { LOG.debug("onWebSocketText({})", message); calls.incrementAndGet(); if (message.equalsIgnoreCase("openSessions")) { Collection<WebSocketSession> sessions = container.getOpenSessions(); StringBuilder ret = new StringBuilder(); ret.append("openSessions.size=").append(sessions.size()).append('\n'); int idx = 0; for (WebSocketSession sess : sessions) { ret.append('[').append(idx++).append("] ").append(sess.toString()).append('\n'); } session.getRemote().sendStringByFuture(ret.toString()); session.close(StatusCode.NORMAL, "ContainerSocket"); } else if (message.equalsIgnoreCase("calls")) { session.getRemote().sendStringByFuture(String.format("calls=%,d", calls.get())); } } @Override public void onWebSocketConnect(Session sess) { LOG.debug("onWebSocketConnect({})", sess); this.session = sess; } }
public class WebListenerAnnotationHandler extends AbstractDiscoverableAnnotationHandler { private static final Logger LOG = Log.getLogger(WebListenerAnnotationHandler.class); public WebListenerAnnotationHandler(WebAppContext context) { super(context); } public void handle(ClassInfo info, String annotationName) { if (annotationName == null || !"javax.servlet.annotation.WebListener".equals(annotationName)) return; WebListenerAnnotation wlAnnotation = new WebListenerAnnotation(_context, info.getClassName(), info.getContainingResource()); addAnnotation(wlAnnotation); } public void handle(FieldInfo info, String annotationName) { if (annotationName == null || !"javax.servlet.annotation.WebListener".equals(annotationName)) return; LOG.warn( "@WebListener is not applicable to fields: " + info.getClassInfo().getClassName() + "." + info.getFieldName()); } public void handle(MethodInfo info, String annotationName) { if (annotationName == null || !"javax.servlet.annotation.WebListener".equals(annotationName)) return; LOG.warn( "@WebListener is not applicable to methods: " + info.getClassInfo().getClassName() + "." + info.getMethodName() + " " + info.getSignature()); } }
public class PlatformAnnotationConfiguration extends AnnotationConfiguration { private static final Logger LOG = Log.getLogger(PlatformAnnotationConfiguration.class); private List<Resource> getResources(ClassLoader aLoader) throws IOException { if (aLoader instanceof URLClassLoader) { List<Resource> _result = new ArrayList<Resource>(); URL[] _urls = ((URLClassLoader) aLoader).getURLs(); for (URL _url : _urls) { _result.add(Resource.newResource(_url)); } return _result; } return Collections.emptyList(); } public void parseContainerPath(final WebAppContext context, final AnnotationParser parser) throws Exception { final Set<Handler> handlers = new HashSet<Handler>(); handlers.addAll(_discoverableAnnotationHandlers); handlers.addAll(_containerInitializerAnnotationHandlers); if (_classInheritanceHandler != null) { handlers.add(_classInheritanceHandler); } _containerPathStats = new CounterStatistic(); List<Resource> _resources = getResources(getClass().getClassLoader()); for (Resource r : _resources) { if (_parserTasks != null) { ParserTask task = new ParserTask(parser, handlers, r, _containerClassNameResolver); _parserTasks.add(task); _containerPathStats.increment(); if (LOG.isDebugEnabled()) { task.setStatistic(new TimeStatistic()); } } } } }
/** * Used to discover the rest of the server URIs through a HTTP GET request to the server root (/). */ @Path("/") public class DiscoveryService { private static final Logger LOGGER = Log.getLogger(DiscoveryService.class); private final Configuration configuration; private final OutputFormat outputFormat; public DiscoveryService( @Context Configuration configuration, @Context OutputFormat outputFormat) { this.configuration = configuration; this.outputFormat = outputFormat; } @GET @Produces(MediaType.APPLICATION_JSON) public Response getDiscoveryDocument() throws URISyntaxException { String webAdminManagementUri = configuration.getString( Configurator.MANAGEMENT_PATH_PROPERTY_KEY, Configurator.DEFAULT_MANAGEMENT_API_PATH); String dataUri = configuration.getString( Configurator.REST_API_PATH_PROPERTY_KEY, Configurator.DEFAULT_DATA_API_PATH); DiscoveryRepresentation dr = new DiscoveryRepresentation(webAdminManagementUri, dataUri); return outputFormat.ok(dr); } @GET @Produces(MediaType.WILDCARD) public Response redirectToBrowser() { try { return Response.seeOther(new URI(Configurator.BROWSER_PATH)).build(); } catch (URISyntaxException e) { LOGGER.warn(e.getMessage()); return Response.serverError().build(); } } }
@ServerEndpoint("/echo/primitives/short") public class ShortTextSocket { private static final Logger LOG = Log.getLogger(ShortTextSocket.class); private Session session; @OnOpen public void onOpen(Session session) { this.session = session; } @OnMessage public void onMessage(short s) throws IOException { String msg = Short.toString(s); session.getAsyncRemote().sendText(msg); } @OnError public void onError(Throwable cause) throws IOException { LOG.warn("Error", cause); session.getBasicRemote().sendText("Exception: " + StackUtil.toString(cause)); } }
/** * {@link HttpOutput} implements {@link ServletOutputStream} as required by the Servlet * specification. * * <p>{@link HttpOutput} buffers content written by the application until a further write will * overflow the buffer, at which point it triggers a commit of the response. * * <p>{@link HttpOutput} can be closed and reopened, to allow requests included via {@link * RequestDispatcher#include(ServletRequest, ServletResponse)} to close the stream, to be reopened * after the inclusion ends. */ public class HttpOutput extends ServletOutputStream implements Runnable { public interface Interceptor { void write(ByteBuffer content, boolean complete, Callback callback); Interceptor getNextInterceptor(); boolean isOptimizedForDirectBuffers(); } private static Logger LOG = Log.getLogger(HttpOutput.class); private final HttpChannel _channel; private final SharedBlockingCallback _writeBlock; private Interceptor _interceptor; /** * Bytes written via the write API (excludes bytes written via sendContent). Used to autocommit * once content length is written. */ private long _written; private ByteBuffer _aggregate; private int _bufferSize; private int _commitSize; private WriteListener _writeListener; private volatile Throwable _onError; /* ACTION OPEN ASYNC READY PENDING UNREADY CLOSED ------------------------------------------------------------------------------------------- setWriteListener() READY->owp ise ise ise ise ise write() OPEN ise PENDING wpe wpe eof flush() OPEN ise PENDING wpe wpe eof close() CLOSED CLOSED CLOSED CLOSED CLOSED CLOSED isReady() OPEN:true READY:true READY:true UNREADY:false UNREADY:false CLOSED:true write completed - - - ASYNC READY->owp - */ private enum OutputState { OPEN, ASYNC, READY, PENDING, UNREADY, ERROR, CLOSED } private final AtomicReference<OutputState> _state = new AtomicReference<>(OutputState.OPEN); public HttpOutput(HttpChannel channel) { _channel = channel; _interceptor = channel; _writeBlock = new SharedBlockingCallback() { @Override protected long getIdleTimeout() { long bto = getHttpChannel().getHttpConfiguration().getBlockingTimeout(); if (bto > 0) return bto; if (bto < 0) return -1; return _channel.getIdleTimeout(); } }; HttpConfiguration config = channel.getHttpConfiguration(); _bufferSize = config.getOutputBufferSize(); _commitSize = config.getOutputAggregationSize(); if (_commitSize > _bufferSize) { LOG.warn("OutputAggregationSize {} exceeds bufferSize {}", _commitSize, _bufferSize); _commitSize = _bufferSize; } } public HttpChannel getHttpChannel() { return _channel; } public Interceptor getInterceptor() { return _interceptor; } public void setInterceptor(Interceptor filter) { _interceptor = filter; } public boolean isWritten() { return _written > 0; } public long getWritten() { return _written; } public void reopen() { _state.set(OutputState.OPEN); } public boolean isAllContentWritten() { return _channel.getResponse().isAllContentWritten(_written); } protected Blocker acquireWriteBlockingCallback() throws IOException { return _writeBlock.acquire(); } private void write(ByteBuffer content, boolean complete) throws IOException { try (Blocker blocker = _writeBlock.acquire()) { write(content, complete, blocker); blocker.block(); } catch (Exception failure) { if (LOG.isDebugEnabled()) LOG.debug(failure); abort(failure); if (failure instanceof IOException) throw failure; throw new IOException(failure); } } protected void write(ByteBuffer content, boolean complete, Callback callback) { _interceptor.write(content, complete, callback); } private void abort(Throwable failure) { closed(); _channel.abort(failure); } @Override public void close() { while (true) { OutputState state = _state.get(); switch (state) { case CLOSED: { return; } case ASYNC: case UNREADY: case PENDING: { if (!_state.compareAndSet(state, OutputState.CLOSED)) break; IOException ex = new IOException("Closed while Pending/Unready"); LOG.warn(ex.toString()); LOG.debug(ex); _channel.abort(ex); } default: { if (!_state.compareAndSet(state, OutputState.CLOSED)) break; try { write( BufferUtil.hasContent(_aggregate) ? _aggregate : BufferUtil.EMPTY_BUFFER, !_channel.getResponse().isIncluding()); } catch (IOException x) { // Ignore it, it's been already logged in write(). } finally { releaseBuffer(); } // Return even if an exception is thrown by write(). return; } } } } /** * Called to indicate that the last write has been performed. It updates the state and performs * cleanup operations. */ void closed() { while (true) { OutputState state = _state.get(); switch (state) { case CLOSED: { return; } case UNREADY: { if (_state.compareAndSet(state, OutputState.ERROR)) _writeListener.onError( _onError == null ? new EofException("Async closed") : _onError); break; } default: { if (!_state.compareAndSet(state, OutputState.CLOSED)) break; try { _channel.getResponse().closeOutput(); } catch (Throwable x) { if (LOG.isDebugEnabled()) LOG.debug(x); abort(x); } finally { releaseBuffer(); } // Return even if an exception is thrown by closeOutput(). return; } } } } private void releaseBuffer() { if (_aggregate != null) { _channel.getConnector().getByteBufferPool().release(_aggregate); _aggregate = null; } } public boolean isClosed() { return _state.get() == OutputState.CLOSED; } public boolean isAsync() { switch (_state.get()) { case ASYNC: case READY: case PENDING: case UNREADY: return true; default: return false; } } @Override public void flush() throws IOException { while (true) { switch (_state.get()) { case OPEN: write(BufferUtil.hasContent(_aggregate) ? _aggregate : BufferUtil.EMPTY_BUFFER, false); return; case ASYNC: throw new IllegalStateException("isReady() not called"); case READY: if (!_state.compareAndSet(OutputState.READY, OutputState.PENDING)) continue; new AsyncFlush().iterate(); return; case PENDING: return; case UNREADY: throw new WritePendingException(); case ERROR: throw new EofException(_onError); case CLOSED: return; default: throw new IllegalStateException(); } } } @Override public void write(byte[] b, int off, int len) throws IOException { _written += len; boolean complete = _channel.getResponse().isAllContentWritten(_written); // Async or Blocking ? while (true) { switch (_state.get()) { case OPEN: // process blocking below break; case ASYNC: throw new IllegalStateException("isReady() not called"); case READY: if (!_state.compareAndSet(OutputState.READY, OutputState.PENDING)) continue; // Should we aggregate? if (!complete && len <= _commitSize) { if (_aggregate == null) _aggregate = _channel .getByteBufferPool() .acquire(getBufferSize(), _interceptor.isOptimizedForDirectBuffers()); // YES - fill the aggregate with content from the buffer int filled = BufferUtil.fill(_aggregate, b, off, len); // return if we are not complete, not full and filled all the content if (filled == len && !BufferUtil.isFull(_aggregate)) { if (!_state.compareAndSet(OutputState.PENDING, OutputState.ASYNC)) throw new IllegalStateException(); return; } // adjust offset/length off += filled; len -= filled; } // Do the asynchronous writing from the callback new AsyncWrite(b, off, len, complete).iterate(); return; case PENDING: case UNREADY: throw new WritePendingException(); case ERROR: throw new EofException(_onError); case CLOSED: throw new EofException("Closed"); default: throw new IllegalStateException(); } break; } // handle blocking write // Should we aggregate? int capacity = getBufferSize(); if (!complete && len <= _commitSize) { if (_aggregate == null) _aggregate = _channel .getByteBufferPool() .acquire(capacity, _interceptor.isOptimizedForDirectBuffers()); // YES - fill the aggregate with content from the buffer int filled = BufferUtil.fill(_aggregate, b, off, len); // return if we are not complete, not full and filled all the content if (filled == len && !BufferUtil.isFull(_aggregate)) return; // adjust offset/length off += filled; len -= filled; } // flush any content from the aggregate if (BufferUtil.hasContent(_aggregate)) { write(_aggregate, complete && len == 0); // should we fill aggregate again from the buffer? if (len > 0 && !complete && len <= _commitSize && len <= BufferUtil.space(_aggregate)) { BufferUtil.append(_aggregate, b, off, len); return; } } // write any remaining content in the buffer directly if (len > 0) { ByteBuffer wrap = ByteBuffer.wrap(b, off, len); ByteBuffer view = wrap.duplicate(); // write a buffer capacity at a time to avoid JVM pooling large direct buffers // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6210541 while (len > getBufferSize()) { int p = view.position(); int l = p + getBufferSize(); view.limit(p + getBufferSize()); write(view, false); len -= getBufferSize(); view.limit(l + Math.min(len, getBufferSize())); view.position(l); } write(view, complete); } else if (complete) { write(BufferUtil.EMPTY_BUFFER, true); } if (complete) closed(); } public void write(ByteBuffer buffer) throws IOException { _written += buffer.remaining(); boolean complete = _channel.getResponse().isAllContentWritten(_written); // Async or Blocking ? while (true) { switch (_state.get()) { case OPEN: // process blocking below break; case ASYNC: throw new IllegalStateException("isReady() not called"); case READY: if (!_state.compareAndSet(OutputState.READY, OutputState.PENDING)) continue; // Do the asynchronous writing from the callback new AsyncWrite(buffer, complete).iterate(); return; case PENDING: case UNREADY: throw new WritePendingException(); case ERROR: throw new EofException(_onError); case CLOSED: throw new EofException("Closed"); default: throw new IllegalStateException(); } break; } // handle blocking write int len = BufferUtil.length(buffer); // flush any content from the aggregate if (BufferUtil.hasContent(_aggregate)) write(_aggregate, complete && len == 0); // write any remaining content in the buffer directly if (len > 0) write(buffer, complete); else if (complete) write(BufferUtil.EMPTY_BUFFER, true); if (complete) closed(); } @Override public void write(int b) throws IOException { _written += 1; boolean complete = _channel.getResponse().isAllContentWritten(_written); // Async or Blocking ? while (true) { switch (_state.get()) { case OPEN: if (_aggregate == null) _aggregate = _channel .getByteBufferPool() .acquire(getBufferSize(), _interceptor.isOptimizedForDirectBuffers()); BufferUtil.append(_aggregate, (byte) b); // Check if all written or full if (complete || BufferUtil.isFull(_aggregate)) { write(_aggregate, complete); if (complete) closed(); } break; case ASYNC: throw new IllegalStateException("isReady() not called"); case READY: if (!_state.compareAndSet(OutputState.READY, OutputState.PENDING)) continue; if (_aggregate == null) _aggregate = _channel .getByteBufferPool() .acquire(getBufferSize(), _interceptor.isOptimizedForDirectBuffers()); BufferUtil.append(_aggregate, (byte) b); // Check if all written or full if (!complete && !BufferUtil.isFull(_aggregate)) { if (!_state.compareAndSet(OutputState.PENDING, OutputState.ASYNC)) throw new IllegalStateException(); return; } // Do the asynchronous writing from the callback new AsyncFlush().iterate(); return; case PENDING: case UNREADY: throw new WritePendingException(); case ERROR: throw new EofException(_onError); case CLOSED: throw new EofException("Closed"); default: throw new IllegalStateException(); } break; } } @Override public void print(String s) throws IOException { if (isClosed()) throw new IOException("Closed"); write(s.getBytes(_channel.getResponse().getCharacterEncoding())); } /** * Blocking send of whole content. * * @param content The whole content to send * @throws IOException if the send fails */ public void sendContent(ByteBuffer content) throws IOException { if (LOG.isDebugEnabled()) LOG.debug("sendContent({})", BufferUtil.toDetailString(content)); write(content, true); closed(); } /** * Blocking send of stream content. * * @param in The stream content to send * @throws IOException if the send fails */ public void sendContent(InputStream in) throws IOException { try (Blocker blocker = _writeBlock.acquire()) { new InputStreamWritingCB(in, blocker).iterate(); blocker.block(); } catch (Throwable failure) { if (LOG.isDebugEnabled()) LOG.debug(failure); abort(failure); throw failure; } } /** * Blocking send of channel content. * * @param in The channel content to send * @throws IOException if the send fails */ public void sendContent(ReadableByteChannel in) throws IOException { try (Blocker blocker = _writeBlock.acquire()) { new ReadableByteChannelWritingCB(in, blocker).iterate(); blocker.block(); } catch (Throwable failure) { if (LOG.isDebugEnabled()) LOG.debug(failure); abort(failure); throw failure; } } /** * Blocking send of HTTP content. * * @param content The HTTP content to send * @throws IOException if the send fails */ public void sendContent(HttpContent content) throws IOException { try (Blocker blocker = _writeBlock.acquire()) { sendContent(content, blocker); blocker.block(); } catch (Throwable failure) { if (LOG.isDebugEnabled()) LOG.debug(failure); abort(failure); throw failure; } } /** * Asynchronous send of whole content. * * @param content The whole content to send * @param callback The callback to use to notify success or failure */ public void sendContent(ByteBuffer content, final Callback callback) { if (LOG.isDebugEnabled()) LOG.debug("sendContent(buffer={},{})", BufferUtil.toDetailString(content), callback); write( content, true, new Callback() { @Override public void succeeded() { closed(); callback.succeeded(); } @Override public void failed(Throwable x) { abort(x); callback.failed(x); } }); } /** * Asynchronous send of stream content. The stream will be closed after reading all content. * * @param in The stream content to send * @param callback The callback to use to notify success or failure */ public void sendContent(InputStream in, Callback callback) { if (LOG.isDebugEnabled()) LOG.debug("sendContent(stream={},{})", in, callback); new InputStreamWritingCB(in, callback).iterate(); } /** * Asynchronous send of channel content. The channel will be closed after reading all content. * * @param in The channel content to send * @param callback The callback to use to notify success or failure */ public void sendContent(ReadableByteChannel in, Callback callback) { if (LOG.isDebugEnabled()) LOG.debug("sendContent(channel={},{})", in, callback); new ReadableByteChannelWritingCB(in, callback).iterate(); } /** * Asynchronous send of HTTP content. * * @param httpContent The HTTP content to send * @param callback The callback to use to notify success or failure */ public void sendContent(HttpContent httpContent, Callback callback) { if (LOG.isDebugEnabled()) LOG.debug("sendContent(http={},{})", httpContent, callback); if (BufferUtil.hasContent(_aggregate)) { callback.failed(new IOException("cannot sendContent() after write()")); return; } if (_channel.isCommitted()) { callback.failed(new IOException("committed")); return; } while (true) { switch (_state.get()) { case OPEN: if (!_state.compareAndSet(OutputState.OPEN, OutputState.PENDING)) continue; break; case ERROR: callback.failed(new EofException(_onError)); return; case CLOSED: callback.failed(new EofException("Closed")); return; default: throw new IllegalStateException(); } break; } ByteBuffer buffer = _channel.useDirectBuffers() ? httpContent.getDirectBuffer() : null; if (buffer == null) buffer = httpContent.getIndirectBuffer(); if (buffer != null) { sendContent(buffer, callback); return; } try { ReadableByteChannel rbc = httpContent.getReadableByteChannel(); if (rbc != null) { // Close of the rbc is done by the async sendContent sendContent(rbc, callback); return; } InputStream in = httpContent.getInputStream(); if (in != null) { sendContent(in, callback); return; } throw new IllegalArgumentException("unknown content for " + httpContent); } catch (Throwable th) { abort(th); callback.failed(th); } } public int getBufferSize() { return _bufferSize; } public void setBufferSize(int size) { _bufferSize = size; _commitSize = size; } public void recycle() { resetBuffer(); _interceptor = _channel; } public void resetBuffer() { _written = 0; if (BufferUtil.hasContent(_aggregate)) BufferUtil.clear(_aggregate); reopen(); } @Override public void setWriteListener(WriteListener writeListener) { if (!_channel.getState().isAsync()) throw new IllegalStateException("!ASYNC"); if (_state.compareAndSet(OutputState.OPEN, OutputState.READY)) { _writeListener = writeListener; if (_channel.getState().onWritePossible()) _channel.execute(_channel); } else throw new IllegalStateException(); } /** @see javax.servlet.ServletOutputStream#isReady() */ @Override public boolean isReady() { while (true) { switch (_state.get()) { case OPEN: return true; case ASYNC: if (!_state.compareAndSet(OutputState.ASYNC, OutputState.READY)) continue; return true; case READY: return true; case PENDING: if (!_state.compareAndSet(OutputState.PENDING, OutputState.UNREADY)) continue; return false; case UNREADY: return false; case ERROR: return true; case CLOSED: return true; default: throw new IllegalStateException(); } } } @Override public void run() { loop: while (true) { OutputState state = _state.get(); if (_onError != null) { switch (state) { case CLOSED: case ERROR: { _onError = null; break loop; } default: { if (_state.compareAndSet(state, OutputState.ERROR)) { Throwable th = _onError; _onError = null; if (LOG.isDebugEnabled()) LOG.debug("onError", th); _writeListener.onError(th); close(); break loop; } } } continue; } switch (_state.get()) { case CLOSED: // Even though a write is not possible, because a close has // occurred, we need to call onWritePossible to tell async // producer that the last write completed. // So fall through case PENDING: case UNREADY: case READY: try { _writeListener.onWritePossible(); break loop; } catch (Throwable e) { _onError = e; } break; default: _onError = new IllegalStateException("state=" + _state.get()); } } } private void close(Closeable resource) { try { resource.close(); } catch (Throwable x) { LOG.ignore(x); } } @Override public String toString() { return String.format("%s@%x{%s}", this.getClass().getSimpleName(), hashCode(), _state.get()); } private abstract class AsyncICB extends IteratingCallback { @Override protected void onCompleteSuccess() { while (true) { OutputState last = _state.get(); switch (last) { case PENDING: if (!_state.compareAndSet(OutputState.PENDING, OutputState.ASYNC)) continue; break; case UNREADY: if (!_state.compareAndSet(OutputState.UNREADY, OutputState.READY)) continue; if (_channel.getState().onWritePossible()) _channel.execute(_channel); break; case CLOSED: break; default: throw new IllegalStateException(); } break; } } @Override public void onCompleteFailure(Throwable e) { _onError = e == null ? new IOException() : e; if (_channel.getState().onWritePossible()) _channel.execute(_channel); } } private class AsyncFlush extends AsyncICB { protected volatile boolean _flushed; public AsyncFlush() {} @Override protected Action process() { if (BufferUtil.hasContent(_aggregate)) { _flushed = true; write(_aggregate, false, this); return Action.SCHEDULED; } if (!_flushed) { _flushed = true; write(BufferUtil.EMPTY_BUFFER, false, this); return Action.SCHEDULED; } return Action.SUCCEEDED; } } private class AsyncWrite extends AsyncICB { private final ByteBuffer _buffer; private final ByteBuffer _slice; private final boolean _complete; private final int _len; protected volatile boolean _completed; public AsyncWrite(byte[] b, int off, int len, boolean complete) { _buffer = ByteBuffer.wrap(b, off, len); _len = len; // always use a view for large byte arrays to avoid JVM pooling large direct buffers _slice = _len < getBufferSize() ? null : _buffer.duplicate(); _complete = complete; } public AsyncWrite(ByteBuffer buffer, boolean complete) { _buffer = buffer; _len = buffer.remaining(); // Use a slice buffer for large indirect to avoid JVM pooling large direct buffers if (_buffer.isDirect() || _len < getBufferSize()) _slice = null; else { _slice = _buffer.duplicate(); _buffer.position(_buffer.limit()); } _complete = complete; } @Override protected Action process() { // flush any content from the aggregate if (BufferUtil.hasContent(_aggregate)) { _completed = _len == 0; write(_aggregate, _complete && _completed, this); return Action.SCHEDULED; } // Can we just aggregate the remainder? if (!_complete && _len < BufferUtil.space(_aggregate) && _len < _commitSize) { int position = BufferUtil.flipToFill(_aggregate); BufferUtil.put(_buffer, _aggregate); BufferUtil.flipToFlush(_aggregate, position); return Action.SUCCEEDED; } // Is there data left to write? if (_buffer.hasRemaining()) { // if there is no slice, just write it if (_slice == null) { _completed = true; write(_buffer, _complete, this); return Action.SCHEDULED; } // otherwise take a slice int p = _buffer.position(); int l = Math.min(getBufferSize(), _buffer.remaining()); int pl = p + l; _slice.limit(pl); _buffer.position(pl); _slice.position(p); _completed = !_buffer.hasRemaining(); write(_slice, _complete && _completed, this); return Action.SCHEDULED; } // all content written, but if we have not yet signal completion, we // need to do so if (_complete && !_completed) { _completed = true; write(BufferUtil.EMPTY_BUFFER, true, this); return Action.SCHEDULED; } if (LOG.isDebugEnabled() && _completed) LOG.debug("EOF of {}", this); return Action.SUCCEEDED; } @Override protected void onCompleteSuccess() { super.onCompleteSuccess(); if (_complete) closed(); } } /** * An iterating callback that will take content from an InputStream and write it to the associated * {@link HttpChannel}. A non direct buffer of size {@link HttpOutput#getBufferSize()} is used. * This callback is passed to the {@link HttpChannel#write(ByteBuffer, boolean, Callback)} to be * notified as each buffer is written and only once all the input is consumed will the wrapped * {@link Callback#succeeded()} method be called. */ private class InputStreamWritingCB extends IteratingNestedCallback { private final InputStream _in; private final ByteBuffer _buffer; private boolean _eof; public InputStreamWritingCB(InputStream in, Callback callback) { super(callback); _in = in; _buffer = _channel.getByteBufferPool().acquire(getBufferSize(), false); } @Override protected Action process() throws Exception { // Only return if EOF has previously been read and thus // a write done with EOF=true if (_eof) { if (LOG.isDebugEnabled()) LOG.debug("EOF of {}", this); // Handle EOF _in.close(); closed(); _channel.getByteBufferPool().release(_buffer); return Action.SUCCEEDED; } // Read until buffer full or EOF int len = 0; while (len < _buffer.capacity() && !_eof) { int r = _in.read(_buffer.array(), _buffer.arrayOffset() + len, _buffer.capacity() - len); if (r < 0) _eof = true; else len += r; } // write what we have _buffer.position(0); _buffer.limit(len); write(_buffer, _eof, this); return Action.SCHEDULED; } @Override public void onCompleteFailure(Throwable x) { abort(x); _channel.getByteBufferPool().release(_buffer); HttpOutput.this.close(_in); super.onCompleteFailure(x); } } /* ------------------------------------------------------------ */ /** * An iterating callback that will take content from a ReadableByteChannel and write it to the * {@link HttpChannel}. A {@link ByteBuffer} of size {@link HttpOutput#getBufferSize()} is used * that will be direct if {@link HttpChannel#useDirectBuffers()} is true. This callback is passed * to the {@link HttpChannel#write(ByteBuffer, boolean, Callback)} to be notified as each buffer * is written and only once all the input is consumed will the wrapped {@link * Callback#succeeded()} method be called. */ private class ReadableByteChannelWritingCB extends IteratingNestedCallback { private final ReadableByteChannel _in; private final ByteBuffer _buffer; private boolean _eof; public ReadableByteChannelWritingCB(ReadableByteChannel in, Callback callback) { super(callback); _in = in; _buffer = _channel.getByteBufferPool().acquire(getBufferSize(), _channel.useDirectBuffers()); } @Override protected Action process() throws Exception { // Only return if EOF has previously been read and thus // a write done with EOF=true if (_eof) { if (LOG.isDebugEnabled()) LOG.debug("EOF of {}", this); _in.close(); closed(); _channel.getByteBufferPool().release(_buffer); return Action.SUCCEEDED; } // Read from stream until buffer full or EOF _buffer.clear(); while (_buffer.hasRemaining() && !_eof) _eof = (_in.read(_buffer)) < 0; // write what we have _buffer.flip(); write(_buffer, _eof, this); return Action.SCHEDULED; } @Override public void onCompleteFailure(Throwable x) { abort(x); _channel.getByteBufferPool().release(_buffer); HttpOutput.this.close(_in); super.onCompleteFailure(x); } } }
/** * HttpGenerator. Builds HTTP Messages. * * <p>If the system property "org.eclipse.jetty.http.HttpGenerator.STRICT" is set to true, then the * generator will strictly pass on the exact strings received from methods and header fields. * Otherwise a fast case insensitive string lookup is used that may alter the case and white space * of some methods/headers */ public class HttpGenerator { private static final Logger LOG = Log.getLogger(HttpGenerator.class); public static final boolean __STRICT = Boolean.getBoolean("org.eclipse.jetty.http.HttpGenerator.STRICT"); private static final byte[] __colon_space = new byte[] {':', ' '}; private static final HttpHeaderValue[] CLOSE = {HttpHeaderValue.CLOSE}; public static final MetaData.Response CONTINUE_100_INFO = new MetaData.Response(HttpVersion.HTTP_1_1, 100, null, null, -1); public static final MetaData.Response PROGRESS_102_INFO = new MetaData.Response(HttpVersion.HTTP_1_1, 102, null, null, -1); public static final MetaData.Response RESPONSE_500_INFO = new MetaData.Response( HttpVersion.HTTP_1_1, HttpStatus.INTERNAL_SERVER_ERROR_500, null, new HttpFields() { { put(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE); } }, 0); // states public enum State { START, COMMITTED, COMPLETING, COMPLETING_1XX, END } public enum Result { NEED_CHUNK, NEED_INFO, NEED_HEADER, FLUSH, CONTINUE, SHUTDOWN_OUT, DONE } // other statics public static final int CHUNK_SIZE = 12; private State _state = State.START; private EndOfContent _endOfContent = EndOfContent.UNKNOWN_CONTENT; private long _contentPrepared = 0; private boolean _noContent = false; private Boolean _persistent = null; private final int _send; private static final int SEND_SERVER = 0x01; private static final int SEND_XPOWEREDBY = 0x02; /* ------------------------------------------------------------------------------- */ public static void setJettyVersion(String serverVersion) { SEND[SEND_SERVER] = StringUtil.getBytes("Server: " + serverVersion + "\015\012"); SEND[SEND_XPOWEREDBY] = StringUtil.getBytes("X-Powered-By: " + serverVersion + "\015\012"); SEND[SEND_SERVER | SEND_XPOWEREDBY] = StringUtil.getBytes( "Server: " + serverVersion + "\015\012X-Powered-By: " + serverVersion + "\015\012"); } /* ------------------------------------------------------------------------------- */ // data private boolean _needCRLF = false; /* ------------------------------------------------------------------------------- */ public HttpGenerator() { this(false, false); } /* ------------------------------------------------------------------------------- */ public HttpGenerator(boolean sendServerVersion, boolean sendXPoweredBy) { _send = (sendServerVersion ? SEND_SERVER : 0) | (sendXPoweredBy ? SEND_XPOWEREDBY : 0); } /* ------------------------------------------------------------------------------- */ public void reset() { _state = State.START; _endOfContent = EndOfContent.UNKNOWN_CONTENT; _noContent = false; _persistent = null; _contentPrepared = 0; _needCRLF = false; } /* ------------------------------------------------------------ */ @Deprecated public boolean getSendServerVersion() { return (_send & SEND_SERVER) != 0; } /* ------------------------------------------------------------ */ @Deprecated public void setSendServerVersion(boolean sendServerVersion) { throw new UnsupportedOperationException(); } /* ------------------------------------------------------------ */ public State getState() { return _state; } /* ------------------------------------------------------------ */ public boolean isState(State state) { return _state == state; } /* ------------------------------------------------------------ */ public boolean isIdle() { return _state == State.START; } /* ------------------------------------------------------------ */ public boolean isEnd() { return _state == State.END; } /* ------------------------------------------------------------ */ public boolean isCommitted() { return _state.ordinal() >= State.COMMITTED.ordinal(); } /* ------------------------------------------------------------ */ public boolean isChunking() { return _endOfContent == EndOfContent.CHUNKED_CONTENT; } /* ------------------------------------------------------------ */ public boolean isNoContent() { return _noContent; } /* ------------------------------------------------------------ */ public void setPersistent(boolean persistent) { _persistent = persistent; } /* ------------------------------------------------------------ */ /** @return true if known to be persistent */ public boolean isPersistent() { return Boolean.TRUE.equals(_persistent); } /* ------------------------------------------------------------ */ public boolean isWritten() { return _contentPrepared > 0; } /* ------------------------------------------------------------ */ public long getContentPrepared() { return _contentPrepared; } /* ------------------------------------------------------------ */ public void abort() { _persistent = false; _state = State.END; _endOfContent = null; } /* ------------------------------------------------------------ */ public Result generateRequest( MetaData.Request info, ByteBuffer header, ByteBuffer chunk, ByteBuffer content, boolean last) throws IOException { switch (_state) { case START: { if (info == null) return Result.NEED_INFO; // Do we need a request header if (header == null) return Result.NEED_HEADER; // If we have not been told our persistence, set the default if (_persistent == null) { _persistent = info.getVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal(); if (!_persistent && HttpMethod.CONNECT.is(info.getMethod())) _persistent = true; } // prepare the header int pos = BufferUtil.flipToFill(header); try { // generate ResponseLine generateRequestLine(info, header); if (info.getVersion() == HttpVersion.HTTP_0_9) _noContent = true; else generateHeaders(info, header, content, last); boolean expect100 = info.getFields().contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString()); if (expect100) { _state = State.COMMITTED; } else { // handle the content. int len = BufferUtil.length(content); if (len > 0) { _contentPrepared += len; if (isChunking()) prepareChunk(header, len); } _state = last ? State.COMPLETING : State.COMMITTED; } return Result.FLUSH; } catch (Exception e) { String message = (e instanceof BufferOverflowException) ? "Request header too large" : e.getMessage(); throw new IOException(message, e); } finally { BufferUtil.flipToFlush(header, pos); } } case COMMITTED: { int len = BufferUtil.length(content); if (len > 0) { // Do we need a chunk buffer? if (isChunking()) { // Do we need a chunk buffer? if (chunk == null) return Result.NEED_CHUNK; BufferUtil.clearToFill(chunk); prepareChunk(chunk, len); BufferUtil.flipToFlush(chunk, 0); } _contentPrepared += len; } if (last) _state = State.COMPLETING; return len > 0 ? Result.FLUSH : Result.CONTINUE; } case COMPLETING: { if (BufferUtil.hasContent(content)) { if (LOG.isDebugEnabled()) LOG.debug("discarding content in COMPLETING"); BufferUtil.clear(content); } if (isChunking()) { // Do we need a chunk buffer? if (chunk == null) return Result.NEED_CHUNK; BufferUtil.clearToFill(chunk); prepareChunk(chunk, 0); BufferUtil.flipToFlush(chunk, 0); _endOfContent = EndOfContent.UNKNOWN_CONTENT; return Result.FLUSH; } _state = State.END; return Boolean.TRUE.equals(_persistent) ? Result.DONE : Result.SHUTDOWN_OUT; } case END: if (BufferUtil.hasContent(content)) { if (LOG.isDebugEnabled()) LOG.debug("discarding content in COMPLETING"); BufferUtil.clear(content); } return Result.DONE; default: throw new IllegalStateException(); } } /* ------------------------------------------------------------ */ public Result generateResponse( MetaData.Response info, ByteBuffer header, ByteBuffer chunk, ByteBuffer content, boolean last) throws IOException { return generateResponse(info, false, header, chunk, content, last); } /* ------------------------------------------------------------ */ public Result generateResponse( MetaData.Response info, boolean head, ByteBuffer header, ByteBuffer chunk, ByteBuffer content, boolean last) throws IOException { switch (_state) { case START: { if (info == null) return Result.NEED_INFO; // Handle 0.9 if (info.getVersion() == HttpVersion.HTTP_0_9) { _persistent = false; _endOfContent = EndOfContent.EOF_CONTENT; if (BufferUtil.hasContent(content)) _contentPrepared += content.remaining(); _state = last ? State.COMPLETING : State.COMMITTED; return Result.FLUSH; } // Do we need a response header if (header == null) return Result.NEED_HEADER; // If we have not been told our persistence, set the default if (_persistent == null) _persistent = (info.getVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal()); // prepare the header int pos = BufferUtil.flipToFill(header); try { // generate ResponseLine generateResponseLine(info, header); // Handle 1xx and no content responses int status = info.getStatus(); if (status >= 100 && status < 200) { _noContent = true; if (status != HttpStatus.SWITCHING_PROTOCOLS_101) { header.put(HttpTokens.CRLF); _state = State.COMPLETING_1XX; return Result.FLUSH; } } else if (status == HttpStatus.NO_CONTENT_204 || status == HttpStatus.NOT_MODIFIED_304) { _noContent = true; } generateHeaders(info, header, content, last); // handle the content. int len = BufferUtil.length(content); if (len > 0) { _contentPrepared += len; if (isChunking() && !head) prepareChunk(header, len); } _state = last ? State.COMPLETING : State.COMMITTED; } catch (Exception e) { String message = (e instanceof BufferOverflowException) ? "Response header too large" : e.getMessage(); throw new IOException(message, e); } finally { BufferUtil.flipToFlush(header, pos); } return Result.FLUSH; } case COMMITTED: { int len = BufferUtil.length(content); // handle the content. if (len > 0) { if (isChunking()) { if (chunk == null) return Result.NEED_CHUNK; BufferUtil.clearToFill(chunk); prepareChunk(chunk, len); BufferUtil.flipToFlush(chunk, 0); } _contentPrepared += len; } if (last) { _state = State.COMPLETING; return len > 0 ? Result.FLUSH : Result.CONTINUE; } return len > 0 ? Result.FLUSH : Result.DONE; } case COMPLETING_1XX: { reset(); return Result.DONE; } case COMPLETING: { if (BufferUtil.hasContent(content)) { if (LOG.isDebugEnabled()) LOG.debug("discarding content in COMPLETING"); BufferUtil.clear(content); } if (isChunking()) { // Do we need a chunk buffer? if (chunk == null) return Result.NEED_CHUNK; // Write the last chunk BufferUtil.clearToFill(chunk); prepareChunk(chunk, 0); BufferUtil.flipToFlush(chunk, 0); _endOfContent = EndOfContent.UNKNOWN_CONTENT; return Result.FLUSH; } _state = State.END; return Boolean.TRUE.equals(_persistent) ? Result.DONE : Result.SHUTDOWN_OUT; } case END: if (BufferUtil.hasContent(content)) { if (LOG.isDebugEnabled()) LOG.debug("discarding content in COMPLETING"); BufferUtil.clear(content); } return Result.DONE; default: throw new IllegalStateException(); } } /* ------------------------------------------------------------ */ private void prepareChunk(ByteBuffer chunk, int remaining) { // if we need CRLF add this to header if (_needCRLF) BufferUtil.putCRLF(chunk); // Add the chunk size to the header if (remaining > 0) { BufferUtil.putHexInt(chunk, remaining); BufferUtil.putCRLF(chunk); _needCRLF = true; } else { chunk.put(LAST_CHUNK); _needCRLF = false; } } /* ------------------------------------------------------------ */ private void generateRequestLine(MetaData.Request request, ByteBuffer header) { header.put(StringUtil.getBytes(request.getMethod())); header.put((byte) ' '); header.put(StringUtil.getBytes(request.getURIString())); switch (request.getVersion()) { case HTTP_1_0: case HTTP_1_1: header.put((byte) ' '); header.put(request.getVersion().toBytes()); break; default: throw new IllegalStateException(); } header.put(HttpTokens.CRLF); } /* ------------------------------------------------------------ */ private void generateResponseLine(MetaData.Response response, ByteBuffer header) { // Look for prepared response line int status = response.getStatus(); PreparedResponse preprepared = status < __preprepared.length ? __preprepared[status] : null; String reason = response.getReason(); if (preprepared != null) { if (reason == null) header.put(preprepared._responseLine); else { header.put(preprepared._schemeCode); header.put(getReasonBytes(reason)); header.put(HttpTokens.CRLF); } } else // generate response line { header.put(HTTP_1_1_SPACE); header.put((byte) ('0' + status / 100)); header.put((byte) ('0' + (status % 100) / 10)); header.put((byte) ('0' + (status % 10))); header.put((byte) ' '); if (reason == null) { header.put((byte) ('0' + status / 100)); header.put((byte) ('0' + (status % 100) / 10)); header.put((byte) ('0' + (status % 10))); } else header.put(getReasonBytes(reason)); header.put(HttpTokens.CRLF); } } /* ------------------------------------------------------------ */ private byte[] getReasonBytes(String reason) { if (reason.length() > 1024) reason = reason.substring(0, 1024); byte[] _bytes = StringUtil.getBytes(reason); for (int i = _bytes.length; i-- > 0; ) if (_bytes[i] == '\r' || _bytes[i] == '\n') _bytes[i] = '?'; return _bytes; } /* ------------------------------------------------------------ */ private void generateHeaders( MetaData _info, ByteBuffer header, ByteBuffer content, boolean last) { final MetaData.Request request = (_info instanceof MetaData.Request) ? (MetaData.Request) _info : null; final MetaData.Response response = (_info instanceof MetaData.Response) ? (MetaData.Response) _info : null; // default field values int send = _send; HttpField transfer_encoding = null; boolean keep_alive = false; boolean close = false; boolean content_type = false; StringBuilder connection = null; // Generate fields if (_info.getFields() != null) { for (HttpField field : _info.getFields()) { String v = field.getValue(); if (v == null || v.length() == 0) continue; // rfc7230 does not allow no value HttpHeader h = field.getHeader(); switch (h == null ? HttpHeader.UNKNOWN : h) { case CONTENT_LENGTH: // handle specially below if (_info.getContentLength() >= 0) _endOfContent = EndOfContent.CONTENT_LENGTH; break; case CONTENT_TYPE: { if (field.getValue().startsWith(MimeTypes.Type.MULTIPART_BYTERANGES.toString())) _endOfContent = EndOfContent.SELF_DEFINING_CONTENT; // write the field to the header content_type = true; putTo(field, header); break; } case TRANSFER_ENCODING: { if (_info.getVersion() == HttpVersion.HTTP_1_1) transfer_encoding = field; // Do NOT add yet! break; } case CONNECTION: { if (request != null) putTo(field, header); // Lookup and/or split connection value field HttpHeaderValue[] values = HttpHeaderValue.CLOSE.is(field.getValue()) ? CLOSE : new HttpHeaderValue[] {HttpHeaderValue.CACHE.get(field.getValue())}; String[] split = null; if (values[0] == null) { split = StringUtil.csvSplit(field.getValue()); if (split.length > 0) { values = new HttpHeaderValue[split.length]; for (int i = 0; i < split.length; i++) values[i] = HttpHeaderValue.CACHE.get(split[i]); } } // Handle connection values for (int i = 0; i < values.length; i++) { HttpHeaderValue value = values[i]; switch (value == null ? HttpHeaderValue.UNKNOWN : value) { case UPGRADE: { // special case for websocket connection ordering header .put(HttpHeader.CONNECTION.getBytesColonSpace()) .put(HttpHeader.UPGRADE.getBytes()); header.put(CRLF); break; } case CLOSE: { close = true; _persistent = false; if (response != null) { if (_endOfContent == EndOfContent.UNKNOWN_CONTENT) _endOfContent = EndOfContent.EOF_CONTENT; } break; } case KEEP_ALIVE: { if (_info.getVersion() == HttpVersion.HTTP_1_0) { keep_alive = true; if (response != null) _persistent = true; } break; } default: { if (connection == null) connection = new StringBuilder(); else connection.append(','); connection.append(split == null ? field.getValue() : split[i]); } } } // Do NOT add yet! break; } case SERVER: { send = send & ~SEND_SERVER; putTo(field, header); break; } default: putTo(field, header); } } } // Calculate how to end _content and connection, _content length and transfer encoding // settings. // From RFC 2616 4.4: // 1. No body for 1xx, 204, 304 & HEAD response // 2. Force _content-length? // 3. If Transfer-Encoding!=identity && HTTP/1.1 && !HttpConnection==close then chunk // 4. Content-Length // 5. multipart/byteranges // 6. close int status = response != null ? response.getStatus() : -1; switch (_endOfContent) { case UNKNOWN_CONTENT: // It may be that we have no _content, or perhaps _content just has not been // written yet? // Response known not to have a body if (_contentPrepared == 0 && response != null && (status < 200 || status == 204 || status == 304)) _endOfContent = EndOfContent.NO_CONTENT; else if (_info.getContentLength() > 0) { // we have been given a content length _endOfContent = EndOfContent.CONTENT_LENGTH; long content_length = _info.getContentLength(); if ((response != null || content_length > 0 || content_type) && !_noContent) { // known length but not actually set. header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace()); BufferUtil.putDecLong(header, content_length); header.put(HttpTokens.CRLF); } } else if (last) { // we have seen all the _content there is, so we can be content-length limited. _endOfContent = EndOfContent.CONTENT_LENGTH; long content_length = _contentPrepared + BufferUtil.length(content); // Do we need to tell the headers about it if ((response != null || content_length > 0 || content_type) && !_noContent) { header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace()); BufferUtil.putDecLong(header, content_length); header.put(HttpTokens.CRLF); } } else { // No idea, so we must assume that a body is coming. _endOfContent = EndOfContent.CHUNKED_CONTENT; // HTTP 1.0 does not understand chunked content, so we must use EOF content. // For a request with HTTP 1.0 & Connection: keep-alive // we *must* close the connection, otherwise the client // has no way to detect the end of the content. if (!isPersistent() || _info.getVersion().ordinal() < HttpVersion.HTTP_1_1.ordinal()) _endOfContent = EndOfContent.EOF_CONTENT; } break; case CONTENT_LENGTH: { long content_length = _info.getContentLength(); if ((response != null || content_length > 0 || content_type) && !_noContent) { header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace()); BufferUtil.putDecLong(header, content_length); header.put(HttpTokens.CRLF); } break; } case SELF_DEFINING_CONTENT: { // TODO - Should we do this? Why was it not required before? long content_length = _info.getContentLength(); if (content_length > 0) { header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace()); BufferUtil.putDecLong(header, content_length); header.put(HttpTokens.CRLF); } break; } case NO_CONTENT: if (response != null && status >= 200 && status != 204 && status != 304) header.put(CONTENT_LENGTH_0); break; case EOF_CONTENT: _persistent = request != null; break; case CHUNKED_CONTENT: break; default: break; } // Add transfer_encoding if needed if (isChunking()) { // try to use user supplied encoding as it may have other values. if (transfer_encoding != null && !HttpHeaderValue.CHUNKED.toString().equalsIgnoreCase(transfer_encoding.getValue())) { String c = transfer_encoding.getValue(); if (c.endsWith(HttpHeaderValue.CHUNKED.toString())) putTo(transfer_encoding, header); else throw new IllegalArgumentException("BAD TE"); } else header.put(TRANSFER_ENCODING_CHUNKED); } // Handle connection if need be if (_endOfContent == EndOfContent.EOF_CONTENT) { keep_alive = false; _persistent = false; } // If this is a response, work out persistence if (response != null) { if (!isPersistent() && (close || _info.getVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal())) { if (connection == null) header.put(CONNECTION_CLOSE); else { header.put(CONNECTION_CLOSE, 0, CONNECTION_CLOSE.length - 2); header.put((byte) ','); header.put(StringUtil.getBytes(connection.toString())); header.put(CRLF); } } else if (keep_alive) { if (connection == null) header.put(CONNECTION_KEEP_ALIVE); else { header.put(CONNECTION_KEEP_ALIVE, 0, CONNECTION_KEEP_ALIVE.length - 2); header.put((byte) ','); header.put(StringUtil.getBytes(connection.toString())); header.put(CRLF); } } else if (connection != null) { header.put(HttpHeader.CONNECTION.getBytesColonSpace()); header.put(StringUtil.getBytes(connection.toString())); header.put(CRLF); } } if (status > 199) header.put(SEND[send]); // end the header. header.put(HttpTokens.CRLF); } /* ------------------------------------------------------------------------------- */ public static byte[] getReasonBuffer(int code) { PreparedResponse status = code < __preprepared.length ? __preprepared[code] : null; if (status != null) return status._reason; return null; } /* ------------------------------------------------------------------------------- */ @Override public String toString() { return String.format("%s@%x{s=%s}", getClass().getSimpleName(), hashCode(), _state); } /* ------------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------------- */ // common _content private static final byte[] LAST_CHUNK = { (byte) '0', (byte) '\015', (byte) '\012', (byte) '\015', (byte) '\012' }; private static final byte[] CONTENT_LENGTH_0 = StringUtil.getBytes("Content-Length: 0\015\012"); private static final byte[] CONNECTION_KEEP_ALIVE = StringUtil.getBytes("Connection: keep-alive\015\012"); private static final byte[] CONNECTION_CLOSE = StringUtil.getBytes("Connection: close\015\012"); private static final byte[] HTTP_1_1_SPACE = StringUtil.getBytes(HttpVersion.HTTP_1_1 + " "); private static final byte[] CRLF = StringUtil.getBytes("\015\012"); private static final byte[] TRANSFER_ENCODING_CHUNKED = StringUtil.getBytes("Transfer-Encoding: chunked\015\012"); private static final byte[][] SEND = new byte[][] { new byte[0], StringUtil.getBytes("Server: Jetty(9.x.x)\015\012"), StringUtil.getBytes("X-Powered-By: Jetty(9.x.x)\015\012"), StringUtil.getBytes("Server: Jetty(9.x.x)\015\012X-Powered-By: Jetty(9.x.x)\015\012") }; /* ------------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------------- */ // Build cache of response lines for status private static class PreparedResponse { byte[] _reason; byte[] _schemeCode; byte[] _responseLine; } private static final PreparedResponse[] __preprepared = new PreparedResponse[HttpStatus.MAX_CODE + 1]; static { int versionLength = HttpVersion.HTTP_1_1.toString().length(); for (int i = 0; i < __preprepared.length; i++) { HttpStatus.Code code = HttpStatus.getCode(i); if (code == null) continue; String reason = code.getMessage(); byte[] line = new byte[versionLength + 5 + reason.length() + 2]; HttpVersion.HTTP_1_1.toBuffer().get(line, 0, versionLength); line[versionLength + 0] = ' '; line[versionLength + 1] = (byte) ('0' + i / 100); line[versionLength + 2] = (byte) ('0' + (i % 100) / 10); line[versionLength + 3] = (byte) ('0' + (i % 10)); line[versionLength + 4] = ' '; for (int j = 0; j < reason.length(); j++) line[versionLength + 5 + j] = (byte) reason.charAt(j); line[versionLength + 5 + reason.length()] = HttpTokens.CARRIAGE_RETURN; line[versionLength + 6 + reason.length()] = HttpTokens.LINE_FEED; __preprepared[i] = new PreparedResponse(); __preprepared[i]._schemeCode = Arrays.copyOfRange(line, 0, versionLength + 5); __preprepared[i]._reason = Arrays.copyOfRange(line, versionLength + 5, line.length - 2); __preprepared[i]._responseLine = line; } } private static void putSanitisedName(String s, ByteBuffer buffer) { int l = s.length(); for (int i = 0; i < l; i++) { char c = s.charAt(i); if (c < 0 || c > 0xff || c == '\r' || c == '\n' || c == ':') buffer.put((byte) '?'); else buffer.put((byte) (0xff & c)); } } private static void putSanitisedValue(String s, ByteBuffer buffer) { int l = s.length(); for (int i = 0; i < l; i++) { char c = s.charAt(i); if (c < 0 || c > 0xff || c == '\r' || c == '\n') buffer.put((byte) ' '); else buffer.put((byte) (0xff & c)); } } public static void putTo(HttpField field, ByteBuffer bufferInFillMode) { if (field instanceof PreEncodedHttpField) { ((PreEncodedHttpField) field).putTo(bufferInFillMode, HttpVersion.HTTP_1_0); } else { HttpHeader header = field.getHeader(); if (header != null) { bufferInFillMode.put(header.getBytesColonSpace()); putSanitisedValue(field.getValue(), bufferInFillMode); } else { putSanitisedName(field.getName(), bufferInFillMode); bufferInFillMode.put(__colon_space); putSanitisedValue(field.getValue(), bufferInFillMode); } BufferUtil.putCRLF(bufferInFillMode); } } public static void putTo(HttpFields fields, ByteBuffer bufferInFillMode) { for (HttpField field : fields) { if (field != null) putTo(field, bufferInFillMode); } BufferUtil.putCRLF(bufferInFillMode); } }
/** * AbstractContextProvider * * <p>Base class for DeploymentManager Providers that can deploy ContextHandlers into Jetty that * have been discovered via OSGI either as bundles or services. */ public abstract class AbstractContextProvider extends AbstractLifeCycle implements AppProvider { private static final Logger LOG = Log.getLogger(AbstractContextProvider.class); private DeploymentManager _deploymentManager; private ServerInstanceWrapper _serverWrapper; /* ------------------------------------------------------------ */ /** OSGiApp */ public class OSGiApp extends AbstractOSGiApp { private String _contextFile; private ContextHandler _contextHandler; private boolean _configured = false; public OSGiApp( DeploymentManager manager, AppProvider provider, String originId, Bundle bundle, String contextFile) { super(manager, provider, bundle, originId); _contextFile = contextFile; } public OSGiApp( DeploymentManager manager, AppProvider provider, Bundle bundle, Dictionary properties, String contextFile, String originId) { super(manager, provider, bundle, properties, originId); _contextFile = contextFile; } public String getContextFile() { return _contextFile; } public void setHandler(ContextHandler h) { _contextHandler = h; } public ContextHandler createContextHandler() throws Exception { configureContextHandler(); return _contextHandler; } public void configureContextHandler() throws Exception { if (_configured) return; _configured = true; // Override for bundle root may have been set String bundleOverrideLocation = (String) _properties.get(OSGiWebappConstants.JETTY_BUNDLE_INSTALL_LOCATION_OVERRIDE); if (bundleOverrideLocation == null) bundleOverrideLocation = (String) _properties.get(OSGiWebappConstants.SERVICE_PROP_BUNDLE_INSTALL_LOCATION_OVERRIDE); // Location on filesystem of bundle or the bundle override location File bundleLocation = BundleFileLocatorHelperFactory.getFactory().getHelper().getBundleInstallLocation(_bundle); File root = (bundleOverrideLocation == null ? bundleLocation : new File(bundleOverrideLocation)); Resource rootResource = Resource.newResource( BundleFileLocatorHelperFactory.getFactory() .getHelper() .getLocalURL(root.toURI().toURL())); // try and make sure the rootResource is useable - if its a jar then make it a jar file url if (rootResource.exists() && !rootResource.isDirectory() && !rootResource.toString().startsWith("jar:")) { Resource jarResource = JarResource.newJarResource(rootResource); if (jarResource.exists() && jarResource.isDirectory()) rootResource = jarResource; } // Set the base resource of the ContextHandler, if not already set, can also be overridden by // the context xml file if (_contextHandler != null && _contextHandler.getBaseResource() == null) { _contextHandler.setBaseResource(rootResource); } // Use a classloader that knows about the common jetty parent loader, and also the bundle OSGiClassLoader classLoader = new OSGiClassLoader(getServerInstanceWrapper().getParentClassLoaderForWebapps(), _bundle); // if there is a context file, find it and apply it if (_contextFile == null && _contextHandler == null) throw new IllegalStateException("No context file or ContextHandler"); if (_contextFile != null) { // apply the contextFile, creating the ContextHandler, the DeploymentManager will register // it in the ContextHandlerCollection Resource res = null; // try to find the context file in the filesystem if (_contextFile.startsWith("/")) res = getFileAsResource(_contextFile); // try to find it relative to jetty home if (res == null) { // See if the specific server we are related to has jetty.home set String jettyHome = (String) getServerInstanceWrapper() .getServer() .getAttribute(OSGiServerConstants.JETTY_HOME); if (jettyHome != null) res = getFileAsResource(jettyHome, _contextFile); // try to see if a SystemProperty for jetty.home is set if (res == null) { jettyHome = System.getProperty(OSGiServerConstants.JETTY_HOME); if (jettyHome != null) { if (jettyHome.startsWith("\"") || jettyHome.startsWith("'")) jettyHome = jettyHome.substring(1); if (jettyHome.endsWith("\"") || (jettyHome.endsWith("'"))) jettyHome = jettyHome.substring(0, jettyHome.length() - 1); res = getFileAsResource(jettyHome, _contextFile); if (LOG.isDebugEnabled()) LOG.debug("jetty home context file:" + res); } } } // try to find it relative to an override location that has been specified if (res == null) { if (bundleOverrideLocation != null) { res = getFileAsResource( Resource.newResource(bundleOverrideLocation).getFile(), _contextFile); if (LOG.isDebugEnabled()) LOG.debug("Bundle override location context file:" + res); } } // try to find it relative to the bundle in which it is being deployed if (res == null) { if (_contextFile.startsWith("./")) _contextFile = _contextFile.substring(1); if (!_contextFile.startsWith("/")) _contextFile = "/" + _contextFile; URL contextURL = _bundle.getEntry(_contextFile); if (contextURL != null) res = Resource.newResource(contextURL); } // apply the context xml file, either to an existing ContextHandler, or letting the // it create the ContextHandler as necessary if (res != null) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); LOG.debug("Context classloader = " + cl); try { Thread.currentThread().setContextClassLoader(classLoader); XmlConfiguration xmlConfiguration = new XmlConfiguration(res.getInputStream()); HashMap properties = new HashMap(); // put the server instance in properties.put("Server", getServerInstanceWrapper().getServer()); // put in the location of the bundle root properties.put("bundle.root", rootResource.toString()); // insert the bundle's location as a property. xmlConfiguration.getProperties().putAll(properties); if (_contextHandler == null) _contextHandler = (ContextHandler) xmlConfiguration.configure(); else xmlConfiguration.configure(_contextHandler); } finally { Thread.currentThread().setContextClassLoader(cl); } } } // Set up the class loader we created _contextHandler.setClassLoader(classLoader); // If a bundle/service property specifies context path, let it override the context xml String contextPath = (String) _properties.get(OSGiWebappConstants.RFC66_WEB_CONTEXTPATH); if (contextPath == null) contextPath = (String) _properties.get(OSGiWebappConstants.SERVICE_PROP_CONTEXT_PATH); if (contextPath != null) _contextHandler.setContextPath(contextPath); // osgi Enterprise Spec r4 p.427 _contextHandler.setAttribute( OSGiWebappConstants.OSGI_BUNDLECONTEXT, _bundle.getBundleContext()); // make sure we protect also the osgi dirs specified by OSGi Enterprise spec String[] targets = _contextHandler.getProtectedTargets(); int length = (targets == null ? 0 : targets.length); String[] updatedTargets = null; if (targets != null) { updatedTargets = new String[length + OSGiWebappConstants.DEFAULT_PROTECTED_OSGI_TARGETS.length]; System.arraycopy(targets, 0, updatedTargets, 0, length); } else updatedTargets = new String[OSGiWebappConstants.DEFAULT_PROTECTED_OSGI_TARGETS.length]; System.arraycopy( OSGiWebappConstants.DEFAULT_PROTECTED_OSGI_TARGETS, 0, updatedTargets, length, OSGiWebappConstants.DEFAULT_PROTECTED_OSGI_TARGETS.length); _contextHandler.setProtectedTargets(updatedTargets); } private Resource getFileAsResource(String dir, String file) { Resource r = null; try { File asFile = new File(dir, file); if (asFile.exists()) r = Resource.newResource(asFile); } catch (Exception e) { r = null; } return r; } private Resource getFileAsResource(String file) { Resource r = null; try { File asFile = new File(file); if (asFile.exists()) r = Resource.newResource(asFile); } catch (Exception e) { r = null; } return r; } private Resource getFileAsResource(File dir, String file) { Resource r = null; try { File asFile = new File(dir, file); if (asFile.exists()) r = Resource.newResource(asFile); } catch (Exception e) { r = null; } return r; } } /* ------------------------------------------------------------ */ public AbstractContextProvider(ServerInstanceWrapper wrapper) { _serverWrapper = wrapper; } /* ------------------------------------------------------------ */ public ServerInstanceWrapper getServerInstanceWrapper() { return _serverWrapper; } /* ------------------------------------------------------------ */ /** * @see org.eclipse.jetty.deploy.AppProvider#createContextHandler(org.eclipse.jetty.deploy.App) */ public ContextHandler createContextHandler(App app) throws Exception { if (app == null) return null; if (!(app instanceof OSGiApp)) throw new IllegalStateException(app + " is not a BundleApp"); // Create a ContextHandler suitable to deploy in OSGi ContextHandler h = ((OSGiApp) app).createContextHandler(); return h; } /* ------------------------------------------------------------ */ public void setDeploymentManager(DeploymentManager deploymentManager) { _deploymentManager = deploymentManager; } /* ------------------------------------------------------------ */ public DeploymentManager getDeploymentManager() { return _deploymentManager; } }
/** @author Mathieu Carbou ([email protected]) */ public final class RedisSessionManager extends SessionManagerSkeleton<RedisSessionManager.RedisSession> { static final Logger LOG = Log.getLogger("com.ovea.jetty.session"); private static final String[] FIELDS = { "id", "created", "accessed", "lastNode", "expiryTime", "lastSaved", "lastAccessed", "maxIdle", "cookieSet", "attributes" }; private final JedisExecutor jedisExecutor; private final Serializer serializer; private long saveIntervalSec = 20; // only persist changes to session access times every 20 secs public RedisSessionManager(JedisPool jedisPool) { this(jedisPool, new XStreamSerializer()); } public RedisSessionManager(String jndiName) { this(jndiName, new XStreamSerializer()); } public RedisSessionManager(JedisPool jedisPool, Serializer serializer) { this.serializer = serializer; this.jedisExecutor = new PooledJedisExecutor(jedisPool); } public RedisSessionManager(final String jndiName, Serializer serializer) { this.serializer = serializer; this.jedisExecutor = new JedisExecutor() { JedisExecutor delegate; @Override public <V> V execute(JedisCallback<V> cb) { if (delegate == null) { try { InitialContext ic = new InitialContext(); JedisPool jedisPool = (JedisPool) ic.lookup(jndiName); delegate = new PooledJedisExecutor(jedisPool); } catch (Exception e) { throw new IllegalStateException( "Unable to find instance of " + JedisExecutor.class.getName() + " in JNDI location " + jndiName + " : " + e.getMessage(), e); } } return delegate.execute(cb); } }; } public void setSaveInterval(long sec) { saveIntervalSec = sec; } @Override public void doStart() throws Exception { serializer.start(); super.doStart(); } @Override public void doStop() throws Exception { super.doStop(); serializer.stop(); } @Override protected void shutdownSessions() throws Exception { // do nothing here } @Override protected RedisSession loadSession(final String clusterId, final RedisSession current) { long now = System.currentTimeMillis(); RedisSession loaded; if (current == null) { LOG.debug( "[RedisSessionManager] loadSession - No session found in cache, loading id={}", clusterId); loaded = loadFromStore(clusterId, current); } else if (current.requestStarted()) { LOG.debug( "[RedisSessionManager] loadSession - Existing session found in cache, loading id={}", clusterId); loaded = loadFromStore(clusterId, current); } else { loaded = current; } if (loaded == null) { LOG.debug( "[RedisSessionManager] loadSession - No session found in Redis for id={}", clusterId); if (current != null) current.invalidate(); } else if (loaded == current) { LOG.debug( "[RedisSessionManager] loadSession - No change found in Redis for session id={}", clusterId); return loaded; } else if (!loaded.lastNode.equals(getSessionIdManager().getWorkerName()) || current == null) { // if the session in the database has not already expired if (loaded.expiryTime * 1000 > now) { // session last used on a different node, or we don't have it in memory loaded.changeLastNode(getSessionIdManager().getWorkerName()); } else { LOG.debug( "[RedisSessionManager] loadSession - Loaded session has expired, id={}", clusterId); loaded = null; } } return loaded; } private RedisSession loadFromStore(final String clusterId, final RedisSession current) { List<String> redisData = jedisExecutor.execute( new JedisCallback<List<String>>() { @Override public List<String> execute(Jedis jedis) { final String key = RedisSessionIdManager.REDIS_SESSION_KEY + clusterId; if (current == null) { return jedis.exists(key) ? jedis.hmget(key, FIELDS) : null; } else { String val = jedis.hget(key, "lastSaved"); if (val == null) { // no session in store return Collections.emptyList(); } if (current.lastSaved != Long.parseLong(val)) { // session has changed - reload return jedis.hmget(key, FIELDS); } else { // session dit not changed in cache since last save return null; } } } }); if (redisData == null) { // case where session has not been modified return current; } if (redisData.isEmpty() || redisData.get(0) == null) { // no session found in redis (no data) return null; } Map<String, String> data = new HashMap<String, String>(); for (int i = 0; i < FIELDS.length; i++) data.put(FIELDS[i], redisData.get(i)); String attrs = data.get("attributes"); //noinspection unchecked return new RedisSession( data, attrs == null ? new HashMap<String, Object>() : serializer.deserialize(attrs, Map.class)); } @Override protected void storeSession(final RedisSession session) { if (!session.redisMap.isEmpty()) { final Map<String, String> toStore = session.redisMap.containsKey("attributes") ? session.redisMap : new TreeMap<String, String>(session.redisMap); if (toStore.containsKey("attributes")) toStore.put("attributes", serializer.serialize(session.getSessionAttributes())); LOG.debug( "[RedisSessionManager] storeSession - Storing session id={}", session.getClusterId()); jedisExecutor.execute( new JedisCallback<Object>() { @Override public Object execute(Jedis jedis) { session.lastSaved = System.currentTimeMillis(); toStore.put("lastSaved", "" + session.lastSaved); return jedis.multi( new TransactionBlock() { @Override public void execute() throws JedisException { final String key = RedisSessionIdManager.REDIS_SESSION_KEY + session.getClusterId(); super.hmset(key, toStore); int ttl = session.getMaxInactiveInterval(); if (ttl > 0) { super.expire(key, ttl); } } }); } }); session.redisMap.clear(); } } @Override protected RedisSession newSession(HttpServletRequest request) { return new RedisSession(request); } @Override protected void deleteSession(final RedisSession session) { LOG.debug( "[RedisSessionManager] deleteSession - Deleting from Redis session id={}", session.getClusterId()); jedisExecutor.execute( new JedisCallback<Object>() { @Override public Object execute(Jedis jedis) { return jedis.del(RedisSessionIdManager.REDIS_SESSION_KEY + session.getClusterId()); } }); } final class RedisSession extends SessionManagerSkeleton.SessionSkeleton { private final Map<String, String> redisMap = new TreeMap<String, String>(); private long expiryTime; private long lastSaved; private String lastNode; private final ThreadLocal<Boolean> firstAccess = new ThreadLocal<Boolean>() { @Override protected Boolean initialValue() { return true; } }; private RedisSession(HttpServletRequest request) { super(request); lastNode = getSessionIdManager().getWorkerName(); long ttl = getMaxInactiveInterval(); expiryTime = ttl <= 0 ? 0 : System.currentTimeMillis() / 1000 + ttl; // new session so prepare redis map accordingly redisMap.put("id", getClusterId()); redisMap.put("context", getCanonicalizedContext()); redisMap.put("virtualHost", getVirtualHost()); redisMap.put("created", "" + getCreationTime()); redisMap.put("lastNode", lastNode); redisMap.put("lastAccessed", "" + getLastAccessedTime()); redisMap.put("accessed", "" + getAccessed()); redisMap.put("expiryTime", "" + expiryTime); redisMap.put("maxIdle", "" + ttl); redisMap.put("cookieSet", "" + getCookieSetTime()); redisMap.put("attributes", ""); } RedisSession(Map<String, String> redisData, Map<String, Object> attributes) { super( parseLong(redisData.get("created")), parseLong(redisData.get("accessed")), redisData.get("id")); lastNode = redisData.get("lastNode"); expiryTime = parseLong(redisData.get("expiryTime")); lastSaved = parseLong(redisData.get("lastSaved")); super.setMaxInactiveInterval(parseInt(redisData.get("maxIdle"))); setCookieSetTime(parseLong(redisData.get("cookieSet"))); for (Map.Entry<String, Object> entry : attributes.entrySet()) { super.doPutOrRemove(entry.getKey(), entry.getValue()); } super.access(parseLong(redisData.get("lastAccessed"))); } public void changeLastNode(String lastNode) { this.lastNode = lastNode; redisMap.put("lastNode", lastNode); } @Override public void setAttribute(String name, Object value) { super.setAttribute(name, value); redisMap.put("attributes", ""); } @Override public void removeAttribute(String name) { super.removeAttribute(name); redisMap.put("attributes", ""); } public final Map<String, Object> getSessionAttributes() { Map<String, Object> attrs = new LinkedHashMap<String, Object>(); for (String key : super.getNames()) { attrs.put(key, super.doGet(key)); } return attrs; } @Override protected boolean access(long time) { boolean ret = super.access(time); firstAccess.remove(); int ttl = getMaxInactiveInterval(); expiryTime = ttl <= 0 ? 0 : time / 1000 + ttl; // prepare serialization redisMap.put("lastAccessed", "" + getLastAccessedTime()); redisMap.put("accessed", "" + getAccessed()); redisMap.put("expiryTime", "" + expiryTime); return ret; } @Override public void setMaxInactiveInterval(int secs) { super.setMaxInactiveInterval(secs); // prepare serialization redisMap.put("maxIdle", "" + secs); } @Override protected void cookieSet() { super.cookieSet(); // prepare serialization redisMap.put("cookieSet", "" + getCookieSetTime()); } @Override protected void complete() { super.complete(); if (!redisMap.isEmpty() && (redisMap.size() != 3 || !redisMap.containsKey("lastAccessed") || !redisMap.containsKey("accessed") || !redisMap.containsKey("expiryTime") || getAccessed() - lastSaved >= saveIntervalSec * 1000)) { try { willPassivate(); storeSession(this); didActivate(); } catch (Exception e) { LOG.warn( "[RedisSessionManager] complete - Problem persisting changed session data id=" + getId(), e); } finally { redisMap.clear(); } } } public boolean requestStarted() { boolean first = firstAccess.get(); if (first) firstAccess.set(false); return first; } } }
@ManagedObject public class WebSocketSession extends ContainerLifeCycle implements Session, WebSocketConnection, IncomingFrames { private static final Logger LOG = Log.getLogger(WebSocketSession.class); private final URI requestURI; private final EventDriver websocket; private final LogicalConnection connection; private ExtensionFactory extensionFactory; private boolean active = false; private long maximumMessageSize; private long inactiveTime; private List<String> negotiatedExtensions = new ArrayList<>(); private String protocolVersion; private String negotiatedSubprotocol; private long timeout; private Map<String, String[]> parameterMap = new HashMap<>(); private WebSocketRemoteEndpoint remote; private IncomingFrames incomingHandler; private OutgoingFrames outgoingHandler; private WebSocketPolicy policy; public WebSocketSession(URI requestURI, EventDriver websocket, LogicalConnection connection) { if (requestURI == null) { throw new RuntimeException("Request URI cannot be null"); } this.requestURI = requestURI; this.websocket = websocket; this.connection = connection; this.outgoingHandler = connection; this.incomingHandler = websocket; // Get the parameter map (use the jetty MultiMap to do this right) MultiMap<String> params = new MultiMap<>(); String query = requestURI.getQuery(); if (StringUtil.isNotBlank(query)) { UrlEncoded.decodeTo(query, params, StringUtil.__UTF8); } for (String name : params.keySet()) { List<String> valueList = params.getValues(name); String valueArr[] = new String[valueList.size()]; valueArr = valueList.toArray(valueArr); parameterMap.put(name, valueArr); } } @Override public void close() throws IOException { connection.close(); } @Override public void close(CloseStatus closeStatus) throws IOException { connection.close(closeStatus.getCode(), closeStatus.getPhrase()); } @Override public void close(int statusCode, String reason) { connection.close(statusCode, reason); } @Override public void dump(Appendable out, String indent) throws IOException { super.dump(out, indent); out.append(indent).append(" +- incomingHandler : "); if (incomingHandler instanceof Dumpable) { ((Dumpable) incomingHandler).dump(out, indent + " "); } else { out.append(incomingHandler.toString()).append('\n'); } out.append(indent).append(" +- outgoingHandler : "); if (outgoingHandler instanceof Dumpable) { ((Dumpable) outgoingHandler).dump(out, indent + " "); } else { out.append(outgoingHandler.toString()).append('\n'); } } public LogicalConnection getConnection() { return connection; } public ExtensionFactory getExtensionFactory() { return extensionFactory; } @ManagedAttribute(readonly = true) public IncomingFrames getIncomingHandler() { return incomingHandler; } @Override public InetSocketAddress getLocalAddress() { return connection.getLocalAddress(); } @Override public long getMaximumMessageSize() { return maximumMessageSize; } @Override public List<String> getNegotiatedExtensions() { return negotiatedExtensions; } @Override public String getNegotiatedSubprotocol() { return negotiatedSubprotocol; } @ManagedAttribute(readonly = true) public OutgoingFrames getOutgoingHandler() { return outgoingHandler; } @Override public WebSocketPolicy getPolicy() { return policy; } @Override public String getProtocolVersion() { return protocolVersion; } @Override public String getQueryString() { return getRequestURI().getQuery(); } @Override public RemoteEndpoint getRemote() { if (!isOpen()) { throw new WebSocketException("Session has not been opened yet"); } return remote; } @Override public InetSocketAddress getRemoteAddress() { return remote.getInetSocketAddress(); } @Override public URI getRequestURI() { return requestURI; } @Override public String getSubProtocol() { return getNegotiatedSubprotocol(); } /** The timeout in seconds */ @Override public long getTimeout() { return timeout; } /** Incoming Errors from Parser */ @Override public void incomingError(WebSocketException e) { if (connection.isInputClosed()) { return; // input is closed } // Forward Errors to User WebSocket Object websocket.incomingError(e); } /** Incoming Raw Frames from Parser */ @Override public void incomingFrame(Frame frame) { if (connection.isInputClosed()) { return; // input is closed } // Forward Frames Through Extension List incomingHandler.incomingFrame(frame); } @Override public boolean isActive() { return active; } @Override public boolean isOpen() { return isActive(); } @Override public boolean isSecure() { return getRequestURI().getScheme().equalsIgnoreCase("wss"); } /** * Open/Activate the session * * @throws IOException */ public void open() { if (isOpen()) { throw new WebSocketException("Cannot Open WebSocketSession, Already open"); } // Connect remote remote = new WebSocketRemoteEndpoint(connection, outgoingHandler); // Activate Session this.active = true; // Open WebSocket websocket.setSession(this); websocket.onConnect(); if (LOG.isDebugEnabled()) { LOG.debug("{}", dump()); } } @Override public void ping(ByteBuffer buf) throws IOException { remote.sendPing(buf); } public void setActive(boolean active) { this.active = active; } public void setExtensionFactory(ExtensionFactory extensionFactory) { this.extensionFactory = extensionFactory; } @Override public void setMaximumMessageSize(long length) { this.maximumMessageSize = length; } public void setNegotiatedExtensions(List<String> negotiatedExtensions) { this.negotiatedExtensions.clear(); this.negotiatedExtensions.addAll(negotiatedExtensions); } public void setNegotiatedSubprotocol(String negotiatedSubprotocol) { this.negotiatedSubprotocol = negotiatedSubprotocol; } public void setOutgoingHandler(OutgoingFrames outgoing) { this.outgoingHandler = outgoing; } public void setPolicy(WebSocketPolicy policy) { this.policy = policy; } /** Set the timeout in seconds */ @Override public void setTimeout(long seconds) { this.timeout = seconds; } @Override public SuspendToken suspend() { // TODO Auto-generated method stub return null; } @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("WebSocketSession["); builder.append("websocket=").append(websocket); builder.append(",connection=").append(connection); builder.append(",remote=").append(remote); builder.append(",incoming=").append(incomingHandler); builder.append(",outgoing=").append(outgoingHandler); builder.append("]"); return builder.toString(); } @Override public Future<WriteResult> write(byte[] buf, int offset, int len) throws IOException { return remote.sendBytesByFuture(ByteBuffer.wrap(buf, offset, len)); } @Override public Future<WriteResult> write(ByteBuffer buffer) throws IOException { return remote.sendBytesByFuture(buffer); } @Override public Future<WriteResult> write(String message) throws IOException { return remote.sendStringByFuture(message); } }
/* ------------------------------------------------------------ */ class JarFileResource extends JarResource { private static final Logger LOG = Log.getLogger(JarFileResource.class); private JarFile _jarFile; private File _file; private String[] _list; private JarEntry _entry; private boolean _directory; private String _jarUrl; private String _path; private boolean _exists; /* -------------------------------------------------------- */ JarFileResource(URL url) { super(url); } /* ------------------------------------------------------------ */ JarFileResource(URL url, boolean useCaches) { super(url, useCaches); } /* ------------------------------------------------------------ */ @Override public synchronized void release() { _list = null; _entry = null; _file = null; _jarFile = null; super.release(); } /* ------------------------------------------------------------ */ @Override protected synchronized boolean checkConnection() { try { super.checkConnection(); } finally { if (_jarConnection == null) { _entry = null; _file = null; _jarFile = null; _list = null; } } return _jarFile != null; } /* ------------------------------------------------------------ */ @Override protected synchronized void newConnection() throws IOException { super.newConnection(); _entry = null; _file = null; _jarFile = null; _list = null; int sep = _urlString.indexOf("!/"); _jarUrl = _urlString.substring(0, sep + 2); _path = _urlString.substring(sep + 2); if (_path.length() == 0) _path = null; _jarFile = _jarConnection.getJarFile(); _file = new File(_jarFile.getName()); } /* ------------------------------------------------------------ */ /** Returns true if the represented resource exists. */ @Override public boolean exists() { if (_exists) return true; if (_urlString.endsWith("!/")) { String file_url = _urlString.substring(4, _urlString.length() - 2); try { return newResource(file_url).exists(); } catch (Exception e) { LOG.ignore(e); return false; } } boolean check = checkConnection(); // Is this a root URL? if (_jarUrl != null && _path == null) { // Then if it exists it is a directory _directory = check; return true; } else { // Can we find a file for it? JarFile jarFile = null; if (check) // Yes jarFile = _jarFile; else { // No - so lets look if the root entry exists. try { JarURLConnection c = (JarURLConnection) ((new URL(_jarUrl)).openConnection()); c.setUseCaches(getUseCaches()); jarFile = c.getJarFile(); } catch (Exception e) { LOG.ignore(e); } } // Do we need to look more closely? if (jarFile != null && _entry == null && !_directory) { // OK - we have a JarFile, lets look at the entries for our path Enumeration<JarEntry> e = jarFile.entries(); while (e.hasMoreElements()) { JarEntry entry = e.nextElement(); String name = entry.getName().replace('\\', '/'); // Do we have a match if (name.equals(_path)) { _entry = entry; // Is the match a directory _directory = _path.endsWith("/"); break; } else if (_path.endsWith("/")) { if (name.startsWith(_path)) { _directory = true; break; } } else if (name.startsWith(_path) && name.length() > _path.length() && name.charAt(_path.length()) == '/') { _directory = true; break; } } if (_directory && !_urlString.endsWith("/")) { _urlString += "/"; try { _url = new URL(_urlString); } catch (MalformedURLException ex) { LOG.warn(ex); } } } } _exists = (_directory || _entry != null); return _exists; } /* ------------------------------------------------------------ */ /** * Returns true if the represented resource is a container/directory. If the resource is not a * file, resources ending with "/" are considered directories. */ @Override public boolean isDirectory() { return _urlString.endsWith("/") || exists() && _directory; } /* ------------------------------------------------------------ */ /** Returns the last modified time */ @Override public long lastModified() { if (checkConnection() && _file != null) { if (exists() && _entry != null) return _entry.getTime(); return _file.lastModified(); } return -1; } /* ------------------------------------------------------------ */ @Override public synchronized String[] list() { if (isDirectory() && _list == null) { List<String> list = null; try { list = listEntries(); } catch (Exception e) { // Sun's JarURLConnection impl for jar: protocol will close a JarFile in its connect() // method if // useCaches == false (eg someone called URLConnection with defaultUseCaches==true). // As their sun.net.www.protocol.jar package caches JarFiles and/or connections, we can wind // up in // the situation where the JarFile we have remembered in our _jarFile member has actually // been closed // by other code. // So, do one retry to drop a connection and get a fresh JarFile LOG.warn("Retrying list:" + e); LOG.debug(e); release(); list = listEntries(); } if (list != null) { _list = new String[list.size()]; list.toArray(_list); } } return _list; } /* ------------------------------------------------------------ */ private List<String> listEntries() { checkConnection(); ArrayList<String> list = new ArrayList<String>(32); JarFile jarFile = _jarFile; if (jarFile == null) { try { JarURLConnection jc = (JarURLConnection) ((new URL(_jarUrl)).openConnection()); jc.setUseCaches(getUseCaches()); jarFile = jc.getJarFile(); } catch (Exception e) { e.printStackTrace(); LOG.ignore(e); } if (jarFile == null) throw new IllegalStateException(); } Enumeration e = jarFile.entries(); String dir = _urlString.substring(_urlString.indexOf("!/") + 2); while (e.hasMoreElements()) { JarEntry entry = (JarEntry) e.nextElement(); String name = entry.getName().replace('\\', '/'); if (!name.startsWith(dir) || name.length() == dir.length()) { continue; } String listName = name.substring(dir.length()); int dash = listName.indexOf('/'); if (dash >= 0) { // when listing jar:file urls, you get back one // entry for the dir itself, which we ignore if (dash == 0 && listName.length() == 1) continue; // when listing jar:file urls, all files and // subdirs have a leading /, which we remove if (dash == 0) listName = listName.substring(dash + 1, listName.length()); else listName = listName.substring(0, dash + 1); if (list.contains(listName)) continue; } list.add(listName); } return list; } /* ------------------------------------------------------------ */ /** Return the length of the resource */ @Override public long length() { if (isDirectory()) return -1; if (_entry != null) return _entry.getSize(); return -1; } /* ------------------------------------------------------------ */ /** * Encode according to this resource type. File URIs are not encoded. * * @param uri URI to encode. * @return The uri unchanged. */ @Override public String encode(String uri) { return uri; } /** * Take a Resource that possibly might use URLConnection caching and turn it into one that * doesn't. * * @param resource * @return the non-caching resource */ public static Resource getNonCachingResource(Resource resource) { if (!(resource instanceof JarFileResource)) return resource; JarFileResource oldResource = (JarFileResource) resource; JarFileResource newResource = new JarFileResource(oldResource.getURL(), false); return newResource; } /** * Check if this jar:file: resource is contained in the named resource. Eg <code> * jar:file:///a/b/c/foo.jar!/x.html</code> isContainedIn <code>file:///a/b/c/foo.jar</code> * * @param resource * @return true if resource is contained in the named resource * @throws MalformedURLException */ @Override public boolean isContainedIn(Resource resource) throws MalformedURLException { String string = _urlString; int index = string.indexOf("!/"); if (index > 0) string = string.substring(0, index); if (string.startsWith("jar:")) string = string.substring(4); URL url = new URL(string); return url.sameFile(resource.getURL()); } }
/** * TYPE Utilities. Provides various static utiltiy methods for manipulating types and their string * representations. * * @since Jetty 4.1 */ public class TypeUtil { private static final Logger LOG = Log.getLogger(TypeUtil.class); public static final Class<?>[] NO_ARGS = new Class[] {}; public static final int CR = '\015'; public static final int LF = '\012'; /* ------------------------------------------------------------ */ private static final HashMap<String, Class<?>> name2Class = new HashMap<>(); static { name2Class.put("boolean", java.lang.Boolean.TYPE); name2Class.put("byte", java.lang.Byte.TYPE); name2Class.put("char", java.lang.Character.TYPE); name2Class.put("double", java.lang.Double.TYPE); name2Class.put("float", java.lang.Float.TYPE); name2Class.put("int", java.lang.Integer.TYPE); name2Class.put("long", java.lang.Long.TYPE); name2Class.put("short", java.lang.Short.TYPE); name2Class.put("void", java.lang.Void.TYPE); name2Class.put("java.lang.Boolean.TYPE", java.lang.Boolean.TYPE); name2Class.put("java.lang.Byte.TYPE", java.lang.Byte.TYPE); name2Class.put("java.lang.Character.TYPE", java.lang.Character.TYPE); name2Class.put("java.lang.Double.TYPE", java.lang.Double.TYPE); name2Class.put("java.lang.Float.TYPE", java.lang.Float.TYPE); name2Class.put("java.lang.Integer.TYPE", java.lang.Integer.TYPE); name2Class.put("java.lang.Long.TYPE", java.lang.Long.TYPE); name2Class.put("java.lang.Short.TYPE", java.lang.Short.TYPE); name2Class.put("java.lang.Void.TYPE", java.lang.Void.TYPE); name2Class.put("java.lang.Boolean", java.lang.Boolean.class); name2Class.put("java.lang.Byte", java.lang.Byte.class); name2Class.put("java.lang.Character", java.lang.Character.class); name2Class.put("java.lang.Double", java.lang.Double.class); name2Class.put("java.lang.Float", java.lang.Float.class); name2Class.put("java.lang.Integer", java.lang.Integer.class); name2Class.put("java.lang.Long", java.lang.Long.class); name2Class.put("java.lang.Short", java.lang.Short.class); name2Class.put("Boolean", java.lang.Boolean.class); name2Class.put("Byte", java.lang.Byte.class); name2Class.put("Character", java.lang.Character.class); name2Class.put("Double", java.lang.Double.class); name2Class.put("Float", java.lang.Float.class); name2Class.put("Integer", java.lang.Integer.class); name2Class.put("Long", java.lang.Long.class); name2Class.put("Short", java.lang.Short.class); name2Class.put(null, java.lang.Void.TYPE); name2Class.put("string", java.lang.String.class); name2Class.put("String", java.lang.String.class); name2Class.put("java.lang.String", java.lang.String.class); } /* ------------------------------------------------------------ */ private static final HashMap<Class<?>, String> class2Name = new HashMap<>(); static { class2Name.put(java.lang.Boolean.TYPE, "boolean"); class2Name.put(java.lang.Byte.TYPE, "byte"); class2Name.put(java.lang.Character.TYPE, "char"); class2Name.put(java.lang.Double.TYPE, "double"); class2Name.put(java.lang.Float.TYPE, "float"); class2Name.put(java.lang.Integer.TYPE, "int"); class2Name.put(java.lang.Long.TYPE, "long"); class2Name.put(java.lang.Short.TYPE, "short"); class2Name.put(java.lang.Void.TYPE, "void"); class2Name.put(java.lang.Boolean.class, "java.lang.Boolean"); class2Name.put(java.lang.Byte.class, "java.lang.Byte"); class2Name.put(java.lang.Character.class, "java.lang.Character"); class2Name.put(java.lang.Double.class, "java.lang.Double"); class2Name.put(java.lang.Float.class, "java.lang.Float"); class2Name.put(java.lang.Integer.class, "java.lang.Integer"); class2Name.put(java.lang.Long.class, "java.lang.Long"); class2Name.put(java.lang.Short.class, "java.lang.Short"); class2Name.put(null, "void"); class2Name.put(java.lang.String.class, "java.lang.String"); } /* ------------------------------------------------------------ */ private static final HashMap<Class<?>, Method> class2Value = new HashMap<>(); static { try { Class<?>[] s = {java.lang.String.class}; class2Value.put(java.lang.Boolean.TYPE, java.lang.Boolean.class.getMethod("valueOf", s)); class2Value.put(java.lang.Byte.TYPE, java.lang.Byte.class.getMethod("valueOf", s)); class2Value.put(java.lang.Double.TYPE, java.lang.Double.class.getMethod("valueOf", s)); class2Value.put(java.lang.Float.TYPE, java.lang.Float.class.getMethod("valueOf", s)); class2Value.put(java.lang.Integer.TYPE, java.lang.Integer.class.getMethod("valueOf", s)); class2Value.put(java.lang.Long.TYPE, java.lang.Long.class.getMethod("valueOf", s)); class2Value.put(java.lang.Short.TYPE, java.lang.Short.class.getMethod("valueOf", s)); class2Value.put(java.lang.Boolean.class, java.lang.Boolean.class.getMethod("valueOf", s)); class2Value.put(java.lang.Byte.class, java.lang.Byte.class.getMethod("valueOf", s)); class2Value.put(java.lang.Double.class, java.lang.Double.class.getMethod("valueOf", s)); class2Value.put(java.lang.Float.class, java.lang.Float.class.getMethod("valueOf", s)); class2Value.put(java.lang.Integer.class, java.lang.Integer.class.getMethod("valueOf", s)); class2Value.put(java.lang.Long.class, java.lang.Long.class.getMethod("valueOf", s)); class2Value.put(java.lang.Short.class, java.lang.Short.class.getMethod("valueOf", s)); } catch (Exception e) { throw new Error(e); } } /* ------------------------------------------------------------ */ /** * Array to List. * * <p>Works like {@link Arrays#asList(Object...)}, but handles null arrays. * * @param a the array to convert to a list * @return a list backed by the array. * @param <T> the array and list entry type */ public static <T> List<T> asList(T[] a) { if (a == null) return Collections.emptyList(); return Arrays.asList(a); } /* ------------------------------------------------------------ */ /** * Class from a canonical name for a type. * * @param name A class or type name. * @return A class , which may be a primitive TYPE field.. */ public static Class<?> fromName(String name) { return name2Class.get(name); } /* ------------------------------------------------------------ */ /** * Canonical name for a type. * * @param type A class , which may be a primitive TYPE field. * @return Canonical name. */ public static String toName(Class<?> type) { return class2Name.get(type); } /* ------------------------------------------------------------ */ /** * Convert String value to instance. * * @param type The class of the instance, which may be a primitive TYPE field. * @param value The value as a string. * @return The value as an Object. */ public static Object valueOf(Class<?> type, String value) { try { if (type.equals(java.lang.String.class)) return value; Method m = class2Value.get(type); if (m != null) return m.invoke(null, value); if (type.equals(java.lang.Character.TYPE) || type.equals(java.lang.Character.class)) return value.charAt(0); Constructor<?> c = type.getConstructor(java.lang.String.class); return c.newInstance(value); } catch (NoSuchMethodException | IllegalAccessException | InstantiationException x) { LOG.ignore(x); } catch (InvocationTargetException x) { if (x.getTargetException() instanceof Error) throw (Error) x.getTargetException(); LOG.ignore(x); } return null; } /* ------------------------------------------------------------ */ /** * Convert String value to instance. * * @param type classname or type (eg int) * @param value The value as a string. * @return The value as an Object. */ public static Object valueOf(String type, String value) { return valueOf(fromName(type), value); } /* ------------------------------------------------------------ */ /** * Parse an int from a substring. Negative numbers are not handled. * * @param s String * @param offset Offset within string * @param length Length of integer or -1 for remainder of string * @param base base of the integer * @return the parsed integer * @throws NumberFormatException if the string cannot be parsed */ public static int parseInt(String s, int offset, int length, int base) throws NumberFormatException { int value = 0; if (length < 0) length = s.length() - offset; for (int i = 0; i < length; i++) { char c = s.charAt(offset + i); int digit = convertHexDigit((int) c); if (digit < 0 || digit >= base) throw new NumberFormatException(s.substring(offset, offset + length)); value = value * base + digit; } return value; } /* ------------------------------------------------------------ */ /** * Parse an int from a byte array of ascii characters. Negative numbers are not handled. * * @param b byte array * @param offset Offset within string * @param length Length of integer or -1 for remainder of string * @param base base of the integer * @return the parsed integer * @throws NumberFormatException if the array cannot be parsed into an integer */ public static int parseInt(byte[] b, int offset, int length, int base) throws NumberFormatException { int value = 0; if (length < 0) length = b.length - offset; for (int i = 0; i < length; i++) { char c = (char) (0xff & b[offset + i]); int digit = c - '0'; if (digit < 0 || digit >= base || digit >= 10) { digit = 10 + c - 'A'; if (digit < 10 || digit >= base) digit = 10 + c - 'a'; } if (digit < 0 || digit >= base) throw new NumberFormatException(new String(b, offset, length)); value = value * base + digit; } return value; } /* ------------------------------------------------------------ */ public static byte[] parseBytes(String s, int base) { byte[] bytes = new byte[s.length() / 2]; for (int i = 0; i < s.length(); i += 2) bytes[i / 2] = (byte) TypeUtil.parseInt(s, i, 2, base); return bytes; } /* ------------------------------------------------------------ */ public static String toString(byte[] bytes, int base) { StringBuilder buf = new StringBuilder(); for (byte b : bytes) { int bi = 0xff & b; int c = '0' + (bi / base) % base; if (c > '9') c = 'a' + (c - '0' - 10); buf.append((char) c); c = '0' + bi % base; if (c > '9') c = 'a' + (c - '0' - 10); buf.append((char) c); } return buf.toString(); } /* ------------------------------------------------------------ */ /** * @param c An ASCII encoded character 0-9 a-f A-F * @return The byte value of the character 0-16. */ public static byte convertHexDigit(byte c) { byte b = (byte) ((c & 0x1f) + ((c >> 6) * 0x19) - 0x10); if (b < 0 || b > 15) throw new NumberFormatException("!hex " + c); return b; } /* ------------------------------------------------------------ */ /** * @param c An ASCII encoded character 0-9 a-f A-F * @return The byte value of the character 0-16. */ public static int convertHexDigit(char c) { int d = ((c & 0x1f) + ((c >> 6) * 0x19) - 0x10); if (d < 0 || d > 15) throw new NumberFormatException("!hex " + c); return d; } /* ------------------------------------------------------------ */ /** * @param c An ASCII encoded character 0-9 a-f A-F * @return The byte value of the character 0-16. */ public static int convertHexDigit(int c) { int d = ((c & 0x1f) + ((c >> 6) * 0x19) - 0x10); if (d < 0 || d > 15) throw new NumberFormatException("!hex " + c); return d; } /* ------------------------------------------------------------ */ public static void toHex(byte b, Appendable buf) { try { int d = 0xf & ((0xF0 & b) >> 4); buf.append((char) ((d > 9 ? ('A' - 10) : '0') + d)); d = 0xf & b; buf.append((char) ((d > 9 ? ('A' - 10) : '0') + d)); } catch (IOException e) { throw new RuntimeException(e); } } /* ------------------------------------------------------------ */ public static void toHex(int value, Appendable buf) throws IOException { int d = 0xf & ((0xF0000000 & value) >> 28); buf.append((char) ((d > 9 ? ('A' - 10) : '0') + d)); d = 0xf & ((0x0F000000 & value) >> 24); buf.append((char) ((d > 9 ? ('A' - 10) : '0') + d)); d = 0xf & ((0x00F00000 & value) >> 20); buf.append((char) ((d > 9 ? ('A' - 10) : '0') + d)); d = 0xf & ((0x000F0000 & value) >> 16); buf.append((char) ((d > 9 ? ('A' - 10) : '0') + d)); d = 0xf & ((0x0000F000 & value) >> 12); buf.append((char) ((d > 9 ? ('A' - 10) : '0') + d)); d = 0xf & ((0x00000F00 & value) >> 8); buf.append((char) ((d > 9 ? ('A' - 10) : '0') + d)); d = 0xf & ((0x000000F0 & value) >> 4); buf.append((char) ((d > 9 ? ('A' - 10) : '0') + d)); d = 0xf & value; buf.append((char) ((d > 9 ? ('A' - 10) : '0') + d)); Integer.toString(0, 36); } /* ------------------------------------------------------------ */ public static void toHex(long value, Appendable buf) throws IOException { toHex((int) (value >> 32), buf); toHex((int) value, buf); } /* ------------------------------------------------------------ */ public static String toHexString(byte b) { return toHexString(new byte[] {b}, 0, 1); } /* ------------------------------------------------------------ */ public static String toHexString(byte[] b) { return toHexString(b, 0, b.length); } /* ------------------------------------------------------------ */ public static String toHexString(byte[] b, int offset, int length) { StringBuilder buf = new StringBuilder(); for (int i = offset; i < offset + length; i++) { int bi = 0xff & b[i]; int c = '0' + (bi / 16) % 16; if (c > '9') c = 'A' + (c - '0' - 10); buf.append((char) c); c = '0' + bi % 16; if (c > '9') c = 'a' + (c - '0' - 10); buf.append((char) c); } return buf.toString(); } /* ------------------------------------------------------------ */ public static byte[] fromHexString(String s) { if (s.length() % 2 != 0) throw new IllegalArgumentException(s); byte[] array = new byte[s.length() / 2]; for (int i = 0; i < array.length; i++) { int b = Integer.parseInt(s.substring(i * 2, i * 2 + 2), 16); array[i] = (byte) (0xff & b); } return array; } public static void dump(Class<?> c) { System.err.println("Dump: " + c); dump(c.getClassLoader()); } public static void dump(ClassLoader cl) { System.err.println("Dump Loaders:"); while (cl != null) { System.err.println(" loader " + cl); cl = cl.getParent(); } } public static Object call(Class<?> oClass, String methodName, Object obj, Object[] arg) throws InvocationTargetException, NoSuchMethodException { Objects.requireNonNull(oClass, "Class cannot be null"); Objects.requireNonNull(methodName, "Method name cannot be null"); if (StringUtil.isBlank(methodName)) { throw new IllegalArgumentException("Method name cannot be blank"); } // Lets just try all methods for now for (Method method : oClass.getMethods()) { if (!method.getName().equals(methodName)) continue; if (method.getParameterTypes().length != arg.length) continue; if (Modifier.isStatic(method.getModifiers()) != (obj == null)) continue; if ((obj == null) && method.getDeclaringClass() != oClass) continue; try { return method.invoke(obj, arg); } catch (IllegalAccessException | IllegalArgumentException e) { LOG.ignore(e); } } // Lets look for a method with optional arguments Object[] args_with_opts = null; for (Method method : oClass.getMethods()) { if (!method.getName().equals(methodName)) continue; if (method.getParameterTypes().length != arg.length + 1) continue; if (!method.getParameterTypes()[arg.length].isArray()) continue; if (Modifier.isStatic(method.getModifiers()) != (obj == null)) continue; if ((obj == null) && method.getDeclaringClass() != oClass) continue; if (args_with_opts == null) args_with_opts = ArrayUtil.addToArray(arg, new Object[] {}, Object.class); try { return method.invoke(obj, args_with_opts); } catch (IllegalAccessException | IllegalArgumentException e) { LOG.ignore(e); } } throw new NoSuchMethodException(methodName); } public static Object construct(Class<?> klass, Object[] arguments) throws InvocationTargetException, NoSuchMethodException { Objects.requireNonNull(klass, "Class cannot be null"); for (Constructor<?> constructor : klass.getConstructors()) { if (arguments == null) { // null arguments in .newInstance() is allowed if (constructor.getParameterTypes().length != 0) continue; } else if (constructor.getParameterTypes().length != arguments.length) continue; try { return constructor.newInstance(arguments); } catch (InstantiationException | IllegalAccessException | IllegalArgumentException e) { LOG.ignore(e); } } throw new NoSuchMethodException("<init>"); } public static Object construct( Class<?> klass, Object[] arguments, Map<String, Object> namedArgMap) throws InvocationTargetException, NoSuchMethodException { Objects.requireNonNull(klass, "Class cannot be null"); Objects.requireNonNull(namedArgMap, "Named Argument Map cannot be null"); for (Constructor<?> constructor : klass.getConstructors()) { if (arguments == null) { // null arguments in .newInstance() is allowed if (constructor.getParameterTypes().length != 0) continue; } else if (constructor.getParameterTypes().length != arguments.length) continue; try { Annotation[][] parameterAnnotations = constructor.getParameterAnnotations(); if (arguments == null || arguments.length == 0) { if (LOG.isDebugEnabled()) LOG.debug("Constructor has no arguments"); return constructor.newInstance(arguments); } else if (parameterAnnotations == null || parameterAnnotations.length == 0) { if (LOG.isDebugEnabled()) LOG.debug("Constructor has no parameter annotations"); return constructor.newInstance(arguments); } else { Object[] swizzled = new Object[arguments.length]; int count = 0; for (Annotation[] annotations : parameterAnnotations) { for (Annotation annotation : annotations) { if (annotation instanceof Name) { Name param = (Name) annotation; if (namedArgMap.containsKey(param.value())) { if (LOG.isDebugEnabled()) LOG.debug("placing named {} in position {}", param.value(), count); swizzled[count] = namedArgMap.get(param.value()); } else { if (LOG.isDebugEnabled()) LOG.debug("placing {} in position {}", arguments[count], count); swizzled[count] = arguments[count]; } ++count; } else { if (LOG.isDebugEnabled()) LOG.debug("passing on annotation {}", annotation); } } } return constructor.newInstance(swizzled); } } catch (InstantiationException | IllegalAccessException | IllegalArgumentException e) { LOG.ignore(e); } } throw new NoSuchMethodException("<init>"); } /* ------------------------------------------------------------ */ /** * @param o Object to test for true * @return True if passed object is not null and is either a Boolean with value true or evaluates * to a string that evaluates to true. */ public static boolean isTrue(Object o) { if (o == null) return false; if (o instanceof Boolean) return ((Boolean) o).booleanValue(); return Boolean.parseBoolean(o.toString()); } /* ------------------------------------------------------------ */ /** * @param o Object to test for false * @return True if passed object is not null and is either a Boolean with value false or evaluates * to a string that evaluates to false. */ public static boolean isFalse(Object o) { if (o == null) return false; if (o instanceof Boolean) return !((Boolean) o).booleanValue(); return "false".equalsIgnoreCase(o.toString()); } }
/** * {@link HttpContent} is a stateful, linear representation of the request content provided by a * {@link ContentProvider} that can be traversed one-way to obtain content buffers to send to a HTTP * server. * * <p>{@link HttpContent} offers the notion of a one-way cursor to traverse the content. The cursor * starts in a virtual "before" position and can be advanced using {@link #advance()} until it * reaches a virtual "after" position where the content is fully consumed. * * <pre> * +---+ +---+ +---+ +---+ +---+ * | | | | | | | | | | * +---+ +---+ +---+ +---+ +---+ * ^ ^ ^ ^ * | | --> advance() | | * | | last | * | | | * before | after * | * current * </pre> * * At each valid (non-before and non-after) cursor position, {@link HttpContent} provides the * following state: * * <ul> * <li>the buffer containing the content to send, via {@link #getByteBuffer()} * <li>a copy of the content buffer that can be used for notifications, via {@link #getContent()} * <li>whether the buffer to write is the last one, via {@link #isLast()} * </ul> * * {@link HttpContent} may not have content, if the related {@link ContentProvider} is {@code null}, * and this is reflected by {@link #hasContent()}. * * <p>{@link HttpContent} may have {@link DeferredContentProvider deferred content}, in which case * {@link #advance()} moves the cursor to a position that provides {@code null} {@link * #getByteBuffer() buffer} and {@link #getContent() content}. When the deferred content is * available, a further call to {@link #advance()} will move the cursor to a position that provides * non {@code null} buffer and content. */ public class HttpContent implements Callback, Closeable { private static final Logger LOG = Log.getLogger(HttpContent.class); private static final ByteBuffer AFTER = ByteBuffer.allocate(0); private final ContentProvider provider; private final Iterator<ByteBuffer> iterator; private ByteBuffer buffer; private volatile ByteBuffer content; public HttpContent(ContentProvider provider) { this.provider = provider; this.iterator = provider == null ? Collections.<ByteBuffer>emptyIterator() : provider.iterator(); } /** @return whether there is any content at all */ public boolean hasContent() { return provider != null; } /** @return whether the cursor points to the last content */ public boolean isLast() { return !iterator.hasNext(); } /** @return the {@link ByteBuffer} containing the content at the cursor's position */ public ByteBuffer getByteBuffer() { return buffer; } /** @return a {@link ByteBuffer#slice()} of {@link #getByteBuffer()} at the cursor's position */ public ByteBuffer getContent() { return content; } /** * Advances the cursor to the next block of content. * * <p>The next block of content may be valid (which yields a non-null buffer returned by {@link * #getByteBuffer()}), but may also be deferred (which yields a null buffer returned by {@link * #getByteBuffer()}). * * <p>If the block of content pointed by the new cursor position is valid, this method returns * true. * * @return true if there is content at the new cursor's position, false otherwise. */ public boolean advance() { if (isLast()) { if (content != AFTER) { content = buffer = AFTER; if (LOG.isDebugEnabled()) LOG.debug("Advanced content past last chunk"); } return false; } else { ByteBuffer buffer = this.buffer = iterator.next(); if (LOG.isDebugEnabled()) LOG.debug("Advanced content to {} chunk {}", isLast() ? "last" : "next", buffer); content = buffer == null ? null : buffer.slice(); return buffer != null; } } /** @return whether the cursor has been advanced past the {@link #isLast() last} position. */ public boolean isConsumed() { return content == AFTER; } @Override public void succeeded() { if (iterator instanceof Callback) ((Callback) iterator).succeeded(); } @Override public void failed(Throwable x) { if (iterator instanceof Callback) ((Callback) iterator).failed(x); } @Override public void close() { try { if (iterator instanceof Closeable) ((Closeable) iterator).close(); } catch (Exception x) { LOG.ignore(x); } } @Override public String toString() { return String.format( "%s@%x - has=%b,last=%b,consumed=%b,buffer=%s", getClass().getSimpleName(), hashCode(), hasContent(), isLast(), isConsumed(), BufferUtil.toDetailString(getContent())); } }
/** * A {@link ContentProvider} for form uploads with the {@code "multipart/form-data"} content type. * * <p>Example usage: * * <pre> * MultiPartContentProvider multiPart = new MultiPartContentProvider(); * multiPart.addPart(new MultiPartContentProvider.FieldPart("field", "foo", StandardCharsets.UTF_8)); * multiPart.addPart(new MultiPartContentProvider.PathPart("icon", Paths.get("/tmp/img.png"), "image/png")); * ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) * .method(HttpMethod.POST) * .content(multiPart) * .send(); * </pre> * * <p>The above example would be the equivalent of submitting this form: * * <pre> * <form method="POST" enctype="multipart/form-data" accept-charset="UTF-8"> * <input type="text" name="field" value="foo" /> * <input type="file" name="icon" /> * </form> * </pre> */ public class MultiPartContentProvider implements ContentProvider.Typed { private static final Logger LOG = Log.getLogger(MultiPartContentProvider.class); private static final byte[] COLON_SPACE_BYTES = new byte[] {':', ' '}; private static final byte[] CR_LF_BYTES = new byte[] {'\r', '\n'}; private final List<Part> parts = new ArrayList<>(); private final ByteBuffer firstBoundary; private final ByteBuffer middleBoundary; private final ByteBuffer onlyBoundary; private final ByteBuffer lastBoundary; private final String contentType; private long length; public MultiPartContentProvider() { this(makeBoundary()); } public MultiPartContentProvider(String boundary) { this.contentType = "multipart/form-data; boundary=" + boundary; String firstBoundaryLine = "--" + boundary + "\r\n"; this.firstBoundary = ByteBuffer.wrap(firstBoundaryLine.getBytes(StandardCharsets.US_ASCII)); String middleBoundaryLine = "\r\n" + firstBoundaryLine; this.middleBoundary = ByteBuffer.wrap(middleBoundaryLine.getBytes(StandardCharsets.US_ASCII)); String onlyBoundaryLine = "--" + boundary + "--\r\n"; this.onlyBoundary = ByteBuffer.wrap(onlyBoundaryLine.getBytes(StandardCharsets.US_ASCII)); String lastBoundaryLine = "\r\n" + onlyBoundaryLine; this.lastBoundary = ByteBuffer.wrap(lastBoundaryLine.getBytes(StandardCharsets.US_ASCII)); this.length = this.onlyBoundary.remaining(); } private static String makeBoundary() { Random random = new Random(); StringBuilder builder = new StringBuilder("JettyHttpClientBoundary"); int length = builder.length(); while (builder.length() < length + 16) { long rnd = random.nextLong(); builder.append(Long.toString(rnd < 0 ? -rnd : rnd, 36)); } builder.setLength(length + 16); return builder.toString(); } /** * Adds the given {@code part}. * * @param part the part to add */ public void addPart(Part part) { parts.add(part); length += length(part); if (LOG.isDebugEnabled()) LOG.debug("Added {}", part); } /** * Removes the given {@code part}. * * @param part the part to remove * @return whether the part was removed */ public boolean remove(Part part) { boolean removed = parts.remove(part); if (removed) { length -= length(part); if (LOG.isDebugEnabled()) LOG.debug("Removed {}", part); } return removed; } private long length(Part part) { // For the first part, length = firstBoundary + \r\n after the // value, which is equal to the length of the middleBoundary. return middleBoundary.remaining() + part.getLength(); } @Override public long getLength() { return length; } @Override public Iterator<ByteBuffer> iterator() { return new MultiPartIterator(parts); } @Override public String getContentType() { return contentType; } private static ByteBuffer convertFields(String contentDisposition, Fields fields) { try { if (fields == null || fields.isEmpty()) { contentDisposition += "\r\n"; return ByteBuffer.wrap(contentDisposition.getBytes(StandardCharsets.US_ASCII)); } ByteArrayOutputStream buffer = new ByteArrayOutputStream((fields.getSize() + 1) * contentDisposition.length()); buffer.write(contentDisposition.getBytes(StandardCharsets.US_ASCII)); for (Fields.Field field : fields) { buffer.write(field.getName().getBytes(StandardCharsets.US_ASCII)); buffer.write(COLON_SPACE_BYTES); buffer.write(field.getValue().getBytes(StandardCharsets.UTF_8)); buffer.write(CR_LF_BYTES); } buffer.write(CR_LF_BYTES); return ByteBuffer.wrap(buffer.toByteArray()); } catch (IOException x) { throw new RuntimeIOException(x); } } private class MultiPartIterator implements Iterator<ByteBuffer>, Closeable { private final List<Part> parts; private final Iterator<ByteBuffer> iterator; private MultiPartIterator(List<Part> parts) { this.parts = parts; if (parts.isEmpty()) { iterator = Stream.of(onlyBoundary.slice()).iterator(); } else { List<ByteBuffer> list = new ArrayList<>(); for (int i = 0; i < parts.size(); ++i) { Part part = parts.get(i); if (i == 0) list.add(firstBoundary.slice()); else list.add(middleBoundary.slice()); list.addAll(part.getByteBuffers()); } list.add(lastBoundary.slice()); iterator = list.iterator(); } } @Override public boolean hasNext() { return iterator.hasNext(); } @Override public ByteBuffer next() { return iterator.next(); } @Override public void close() throws IOException { parts.stream().forEach(Part::close); } } /** A generic part of a multipart form. */ public interface Part extends Closeable { /** @return the length (in bytes) of this part, including headers and content */ long getLength(); /** @return the list of {@code ByteBuffers} representing headers and content of this part */ List<ByteBuffer> getByteBuffers(); /** Closes this part. */ @Override default void close() {} } /** A {@link Part} for text (or binary) fields. */ public static class FieldPart implements Part { private final List<ByteBuffer> buffers = new ArrayList<>(); private final String name; private final long length; /** * Creates a {@code FieldPart} with the given name and the given {@code value} to be encoded * using the given {@code charset}. * * <p>The {@code Content-Type} header will be {@code "text/plain; * charset=<charset_name>"}, with the {@code charset} name. * * @param name the part name * @param value the part content * @param charset the charset to encode the part content */ public FieldPart(String name, String value, Charset charset) { this( name, charset == null ? Charset.defaultCharset().encode(value) : charset.encode(value), makeContentType(charset)); } /** * Creates a {@code FieldPart} with the given name and the given {@code value}. * * <p>All the headers (apart {@code "Content-Disposition"}) are specified in the given {@code * fields} parameter. * * @param name the part name * @param value the part content * @param fields the headers associated with this part */ public FieldPart(String name, ByteBuffer value, Fields fields) { this.name = name; buffers.add(convertFields(name, fields)); buffers.add(value); length = buffers.stream().mapToInt(Buffer::remaining).sum(); } private static Fields makeContentType(Charset encoding) { if (encoding == null) return null; Fields fields = new Fields(); fields.put("Content-Type", "text/plain; charset=" + encoding.name()); return fields; } @Override public long getLength() { return length; } @Override public List<ByteBuffer> getByteBuffers() { return buffers; } private static ByteBuffer convertFields(String name, Fields fields) { String contentDisposition = "Content-Disposition: form-data; name=\"" + name + "\"\r\n"; return MultiPartContentProvider.convertFields(contentDisposition, fields); } @Override public String toString() { return String.format("%s@%x[%s]", getClass().getSimpleName(), hashCode(), name); } } /** * A {@link Part} for file fields. * * <p>Files will be transmitted via {@link FileChannel#map(FileChannel.MapMode, long, long) * mapping} and closed when the whole content has been sent. */ public static class PathPart implements Part { private final List<ByteBuffer> buffers = new ArrayList<>(); private final Path path; private final FileChannel channel; private final long length; /** * Creates a {@code PathPart} with the given {@code name} and referencing the given file {@code * path}. * * <p>The {@code Content-Type} header will default to {@code "application/octet-stream"}. * * @param name the part name * @param path the file path representing the content * @throws IOException if an error occurs while trying to open the file path */ public PathPart(String name, Path path) throws IOException { this(name, path, "application/octet-stream"); } /** * Creates a {@code PathPart} withe the given {@code name} and referencing the given file {@code * path}, with the specified {@code contentType}. * * @param name the part name * @param path the file path representing the content * @param contentType the Content-Type header value * @throws IOException if an error occurs while trying to open the file path */ public PathPart(String name, Path path, String contentType) throws IOException { this(name, path, makeFields(contentType)); } /** * Creates a {@code PathPart} withe the given {@code name} and referencing the given file {@code * path}. * * <p>All the headers (apart {@code "Content-Disposition"}) are specified in the given {@code * fields} parameter. * * @param name the part name * @param path the file path representing the content * @param fields the headers associated with this part * @throws IOException if an error occurs while trying to open the file path */ public PathPart(String name, Path path, Fields fields) throws IOException { this.buffers.add(convertFields(name, path, fields)); this.path = path; this.channel = FileChannel.open(path, StandardOpenOption.READ); this.buffers.addAll(fileMap(channel)); this.length = buffers.stream().mapToInt(Buffer::remaining).sum(); } @Override public long getLength() { return length; } @Override public List<ByteBuffer> getByteBuffers() { return buffers; } private static Fields makeFields(String contentType) { Fields fields = new Fields(); fields.put("Content-Type", contentType); return fields; } private static ByteBuffer convertFields(String name, Path path, Fields fields) { String contentDisposition = "Content-Disposition: form-data; name=\"" + name + "\"; " + "filename=\"" + path.getFileName() + "\"\r\n"; return MultiPartContentProvider.convertFields(contentDisposition, fields); } private static List<ByteBuffer> fileMap(FileChannel channel) throws IOException { List<ByteBuffer> result = new ArrayList<>(); long position = 0; long length = channel.size(); channel.position(position); while (length > 0) { // At most 1 GiB file maps. long size = Math.min(1024 * 1024 * 1024, length); ByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, position, size); result.add(buffer); position += size; length -= size; } return result; } @Override public void close() { try { if (LOG.isDebugEnabled()) LOG.debug("Closing {}", this); channel.close(); } catch (IOException x) { LOG.ignore(x); } } @Override public String toString() { return String.format("%s@%x[%s]", getClass().getSimpleName(), hashCode(), path); } } }
/** * AntWebAppContext * * <p>Extension of WebAppContext to allow configuration via Ant environment. */ public class AntWebAppContext extends WebAppContext { private static final Logger LOG = Log.getLogger(WebAppContext.class); public final AntWebInfConfiguration antWebInfConfiguration = new AntWebInfConfiguration(); public final WebXmlConfiguration webXmlConfiguration = new WebXmlConfiguration(); public final MetaInfConfiguration metaInfConfiguration = new MetaInfConfiguration(); public final FragmentConfiguration fragmentConfiguration = new FragmentConfiguration(); public final EnvConfiguration envConfiguration = new EnvConfiguration(); public final PlusConfiguration plusConfiguration = new PlusConfiguration(); public final AnnotationConfiguration annotationConfiguration = new AnnotationConfiguration(); public final JettyWebXmlConfiguration jettyWebXmlConfiguration = new JettyWebXmlConfiguration(); public final Configuration[] DEFAULT_CONFIGURATIONS = { antWebInfConfiguration, webXmlConfiguration, metaInfConfiguration, fragmentConfiguration, envConfiguration, plusConfiguration, annotationConfiguration, jettyWebXmlConfiguration }; public static final String DEFAULT_CONTAINER_INCLUDE_JAR_PATTERN = ".*/.*jsp-api-[^/]*\\.jar$|.*/.*jsp-[^/]*\\.jar$|.*/.*taglibs[^/]*\\.jar$|.*/.*jstl[^/]*\\.jar$|.*/.*jsf-impl-[^/]*\\.jar$|.*/.*javax.faces-[^/]*\\.jar$|.*/.*myfaces-impl-[^/]*\\.jar$"; /** Location of jetty-env.xml file. */ private File jettyEnvXml; /** List of web application libraries. */ private List libraries = new ArrayList(); /** List of web application class directories. */ private List classes = new ArrayList(); /** context xml file to apply to the webapp */ private File contextXml; /** List of extra scan targets for this web application. */ private FileSet scanTargets; /** context attributes to set * */ private Attributes attributes; private Project project; private List<File> scanFiles; /** Extra scan targets. */ private FileMatchingConfiguration extraScanTargetsConfiguration; private FileMatchingConfiguration librariesConfiguration; public static void dump(ClassLoader loader) { while (loader != null) { System.err.println(loader); if (loader instanceof URLClassLoader) { URL[] urls = ((URLClassLoader) loader).getURLs(); if (urls != null) { for (URL u : urls) System.err.println("\t" + u + "\n"); } } loader = loader.getParent(); } } /** * AntURLClassLoader * * <p>Adapt the AntClassLoader which is not a URLClassLoader - this is needed for jsp to be able * to search the classpath. */ public static class AntURLClassLoader extends URLClassLoader { private AntClassLoader antLoader; public AntURLClassLoader(AntClassLoader antLoader) { super(new URL[] {}, antLoader); this.antLoader = antLoader; } @Override public InputStream getResourceAsStream(String name) { return super.getResourceAsStream(name); } @Override public void close() throws IOException { super.close(); } @Override protected void addURL(URL url) { super.addURL(url); } @Override public URL[] getURLs() { Set<URL> urls = new HashSet<URL>(); // convert urls from antLoader String[] paths = antLoader.getClasspath().split(new String(new char[] {File.pathSeparatorChar})); if (paths != null) { for (String p : paths) { File f = new File(p); try { urls.add(f.toURI().toURL()); } catch (Exception e) { LOG.ignore(e); } } } // add in any that may have been added to us as a URL directly URL[] ourURLS = super.getURLs(); if (ourURLS != null) { for (URL u : ourURLS) urls.add(u); } return urls.toArray(new URL[urls.size()]); } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { return super.findClass(name); } @Override protected Package definePackage(String name, Manifest man, URL url) throws IllegalArgumentException { return super.definePackage(name, man, url); } @Override public URL findResource(String name) { return super.findResource(name); } @Override public Enumeration<URL> findResources(String name) throws IOException { return super.findResources(name); } @Override protected PermissionCollection getPermissions(CodeSource codesource) { return super.getPermissions(codesource); } @Override public Class<?> loadClass(String name) throws ClassNotFoundException { return super.loadClass(name); } @Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { return super.loadClass(name, resolve); } @Override protected Object getClassLoadingLock(String className) { return super.getClassLoadingLock(className); } @Override public URL getResource(String name) { return super.getResource(name); } @Override public Enumeration<URL> getResources(String name) throws IOException { return super.getResources(name); } @Override protected Package definePackage( String name, String specTitle, String specVersion, String specVendor, String implTitle, String implVersion, String implVendor, URL sealBase) throws IllegalArgumentException { return super.definePackage( name, specTitle, specVersion, specVendor, implTitle, implVersion, implVendor, sealBase); } @Override protected Package getPackage(String name) { return super.getPackage(name); } @Override protected Package[] getPackages() { return super.getPackages(); } @Override protected String findLibrary(String libname) { return super.findLibrary(libname); } @Override public void setDefaultAssertionStatus(boolean enabled) { super.setDefaultAssertionStatus(enabled); } @Override public void setPackageAssertionStatus(String packageName, boolean enabled) { super.setPackageAssertionStatus(packageName, enabled); } @Override public void setClassAssertionStatus(String className, boolean enabled) { super.setClassAssertionStatus(className, enabled); } @Override public void clearAssertionStatus() { super.clearAssertionStatus(); } } /** AntServletHolder */ public static class AntServletHolder extends ServletHolder { public AntServletHolder() { super(); } public AntServletHolder(Class<? extends Servlet> servlet) { super(servlet); } public AntServletHolder(Servlet servlet) { super(servlet); } public AntServletHolder(String name, Class<? extends Servlet> servlet) { super(name, servlet); } public AntServletHolder(String name, Servlet servlet) { super(name, servlet); } protected String getSystemClassPath(ClassLoader loader) throws Exception { StringBuilder classpath = new StringBuilder(); while (loader != null) { if (loader instanceof URLClassLoader) { URL[] urls = ((URLClassLoader) loader).getURLs(); if (urls != null) { for (int i = 0; i < urls.length; i++) { Resource resource = Resource.newResource(urls[i]); File file = resource.getFile(); if (file != null && file.exists()) { if (classpath.length() > 0) classpath.append(File.pathSeparatorChar); classpath.append(file.getAbsolutePath()); } } } } else if (loader instanceof AntClassLoader) { classpath.append(((AntClassLoader) loader).getClasspath()); } loader = loader.getParent(); } return classpath.toString(); } } /** AntServletHandler */ public static class AntServletHandler extends ServletHandler { @Override public ServletHolder newServletHolder(Holder.Source source) { return new AntServletHolder(); } } /** * Default constructor. Takes application name as an argument * * @param name web application name. */ public AntWebAppContext(Project project) throws Exception { super(); this.project = project; setConfigurations(DEFAULT_CONFIGURATIONS); setAttribute(WebInfConfiguration.CONTAINER_JAR_PATTERN, DEFAULT_CONTAINER_INCLUDE_JAR_PATTERN); setParentLoaderPriority(true); } /** Adds a new Ant's attributes tag object if it have not been created yet. */ public void addAttributes(Attributes atts) { if (this.attributes != null) { throw new BuildException("Only one <attributes> tag is allowed!"); } this.attributes = atts; } public void addLib(FileSet lib) { libraries.add(lib); } public void addClasses(FileSet classes) { this.classes.add(classes); } @Override protected ServletHandler newServletHandler() { return new AntServletHandler(); } public void setJettyEnvXml(File jettyEnvXml) { this.jettyEnvXml = jettyEnvXml; TaskLog.log( "jetty-env.xml file: = " + (jettyEnvXml == null ? null : jettyEnvXml.getAbsolutePath())); } public File getJettyEnvXml() { return this.jettyEnvXml; } public List getLibraries() { return librariesConfiguration.getBaseDirectories(); } public void addScanTargets(FileSet scanTargets) { if (this.scanTargets != null) { throw new BuildException("Only one <scanTargets> tag is allowed!"); } this.scanTargets = scanTargets; } public List getScanTargetFiles() { if (this.scanTargets == null) return null; FileMatchingConfiguration configuration = new FileMatchingConfiguration(); configuration.addDirectoryScanner(scanTargets.getDirectoryScanner(project)); return configuration.getBaseDirectories(); } public List<File> getScanFiles() { if (scanFiles == null) scanFiles = initScanFiles(); return scanFiles; } public boolean isScanned(File file) { List<File> files = getScanFiles(); if (files == null || files.isEmpty()) return false; return files.contains(file); } public List<File> initScanFiles() { List<File> scanList = new ArrayList<File>(); if (getDescriptor() != null) { try { Resource r = Resource.newResource(getDescriptor()); scanList.add(r.getFile()); } catch (IOException e) { throw new BuildException(e); } } if (getJettyEnvXml() != null) { try { Resource r = Resource.newResource(getJettyEnvXml()); scanList.add(r.getFile()); } catch (IOException e) { throw new BuildException("Problem configuring scanner for jetty-env.xml", e); } } if (getDefaultsDescriptor() != null) { try { if (!AntWebAppContext.WEB_DEFAULTS_XML.equals(getDefaultsDescriptor())) { Resource r = Resource.newResource(getDefaultsDescriptor()); scanList.add(r.getFile()); } } catch (IOException e) { throw new BuildException("Problem configuring scanner for webdefaults.xml", e); } } if (getOverrideDescriptor() != null) { try { Resource r = Resource.newResource(getOverrideDescriptor()); scanList.add(r.getFile()); } catch (IOException e) { throw new BuildException("Problem configuring scanner for webdefaults.xml", e); } } // add any extra classpath and libs List<File> cpFiles = getClassPathFiles(); if (cpFiles != null) scanList.addAll(cpFiles); // any extra scan targets @SuppressWarnings("unchecked") List<File> scanFiles = (List<File>) getScanTargetFiles(); if (scanFiles != null) scanList.addAll(scanFiles); return scanList; } @Override public void setWar(String path) { super.setWar(path); try { Resource war = Resource.newResource(path); if (war.exists() && war.isDirectory() && getDescriptor() == null) { Resource webXml = war.addPath("WEB-INF/web.xml"); setDescriptor(webXml.toString()); } } catch (IOException e) { throw new BuildException(e); } } /** */ public void doStart() { try { TaskLog.logWithTimestamp("Starting web application " + this.getDescriptor()); if (jettyEnvXml != null && jettyEnvXml.exists()) envConfiguration.setJettyEnvXml(Resource.toURL(jettyEnvXml)); ClassLoader parentLoader = this.getClass().getClassLoader(); if (parentLoader instanceof AntClassLoader) parentLoader = new AntURLClassLoader((AntClassLoader) parentLoader); setClassLoader(new WebAppClassLoader(parentLoader, this)); if (attributes != null && attributes.getAttributes() != null) { for (Attribute a : attributes.getAttributes()) setAttribute(a.getName(), a.getValue()); } // apply a context xml file if one was supplied if (contextXml != null) { XmlConfiguration xmlConfiguration = new XmlConfiguration(Resource.toURL(contextXml)); TaskLog.log("Applying context xml file " + contextXml); xmlConfiguration.configure(this); } super.doStart(); } catch (Exception e) { TaskLog.log(e.toString()); } } /** @see WebApplicationProxy#stop() */ public void doStop() { try { scanFiles = null; TaskLog.logWithTimestamp("Stopping web application " + this); Thread.currentThread().sleep(500L); super.doStop(); } catch (InterruptedException e) { TaskLog.log(e.toString()); } catch (Exception e) { TaskLog.log(e.toString()); } } /** @return a list of classpath files (libraries and class directories). */ public List<File> getClassPathFiles() { List<File> classPathFiles = new ArrayList<File>(); Iterator classesIterator = classes.iterator(); while (classesIterator.hasNext()) { FileSet clazz = (FileSet) classesIterator.next(); classPathFiles.add(clazz.getDirectoryScanner(project).getBasedir()); } Iterator iterator = libraries.iterator(); while (iterator.hasNext()) { FileSet library = (FileSet) iterator.next(); String[] includedFiles = library.getDirectoryScanner(project).getIncludedFiles(); File baseDir = library.getDirectoryScanner(project).getBasedir(); for (int i = 0; i < includedFiles.length; i++) { classPathFiles.add(new File(baseDir, includedFiles[i])); } } return classPathFiles; } /** * @return a <code>FileMatchingConfiguration</code> object describing the configuration of all * libraries added to this particular web app (both classes and libraries). */ public FileMatchingConfiguration getLibrariesConfiguration() { FileMatchingConfiguration config = new FileMatchingConfiguration(); Iterator classesIterator = classes.iterator(); while (classesIterator.hasNext()) { FileSet clazz = (FileSet) classesIterator.next(); config.addDirectoryScanner(clazz.getDirectoryScanner(project)); } Iterator librariesIterator = libraries.iterator(); while (librariesIterator.hasNext()) { FileSet library = (FileSet) librariesIterator.next(); config.addDirectoryScanner(library.getDirectoryScanner(project)); } return config; } public File getContextXml() { return contextXml; } public void setContextXml(File contextXml) { this.contextXml = contextXml; } }
public static class HttpProxyClientConnectionFactory implements ClientConnectionFactory { private static final Logger LOG = Log.getLogger(HttpProxyClientConnectionFactory.class); private final ClientConnectionFactory connectionFactory; public HttpProxyClientConnectionFactory(ClientConnectionFactory connectionFactory) { this.connectionFactory = connectionFactory; } @Override public org.eclipse.jetty.io.Connection newConnection( EndPoint endPoint, Map<String, Object> context) throws IOException { @SuppressWarnings("unchecked") Promise<Connection> promise = (Promise<Connection>) context.get(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY); final ProxyPromise proxyPromise = new ProxyPromise(endPoint, promise, context); // Replace the promise with the proxy one context.put(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY, proxyPromise); return connectionFactory.newConnection(endPoint, context); } /** * Decides whether to establish a proxy tunnel using HTTP CONNECT. It is implemented as a * promise because it needs to establish the tunnel after the TCP connection is succeeded, and * needs to notify the nested promise when the tunnel is established (or failed). */ private class ProxyPromise implements Promise<Connection> { private final EndPoint endPoint; private final Promise<Connection> promise; private final Map<String, Object> context; private ProxyPromise( EndPoint endPoint, Promise<Connection> promise, Map<String, Object> context) { this.endPoint = endPoint; this.promise = promise; this.context = context; } @Override public void succeeded(Connection connection) { HttpDestination destination = (HttpDestination) context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY); if (HttpScheme.HTTPS.is(destination.getScheme())) { SslContextFactory sslContextFactory = destination.getHttpClient().getSslContextFactory(); if (sslContextFactory != null) { tunnel(destination, connection); } else { String message = String.format( "Cannot perform requests over SSL, no %s in %s", SslContextFactory.class.getSimpleName(), HttpClient.class.getSimpleName()); promise.failed(new IllegalStateException(message)); } } else { promise.succeeded(connection); } } @Override public void failed(Throwable x) { promise.failed(x); } private void tunnel(HttpDestination destination, final Connection connection) { String target = destination.getOrigin().getAddress().asString(); Origin.Address proxyAddress = destination.getConnectAddress(); HttpClient httpClient = destination.getHttpClient(); Request connect = httpClient .newRequest(proxyAddress.getHost(), proxyAddress.getPort()) .scheme(HttpScheme.HTTP.asString()) .method(HttpMethod.CONNECT) .path(target) .header(HttpHeader.HOST, target) .timeout(httpClient.getConnectTimeout(), TimeUnit.MILLISECONDS); connection.send( connect, new Response.CompleteListener() { @Override public void onComplete(Result result) { if (result.isFailed()) { tunnelFailed(result.getFailure()); } else { Response response = result.getResponse(); if (response.getStatus() == 200) { tunnelSucceeded(); } else { tunnelFailed( new HttpResponseException( "Received " + response + " for " + result.getRequest(), response)); } } } }); } private void tunnelSucceeded() { try { // Replace the promise back with the original context.put(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY, promise); HttpDestination destination = (HttpDestination) context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY); HttpClient client = destination.getHttpClient(); ClientConnectionFactory sslConnectionFactory = new SslClientConnectionFactory( client.getSslContextFactory(), client.getByteBufferPool(), client.getExecutor(), connectionFactory); org.eclipse.jetty.io.Connection oldConnection = endPoint.getConnection(); org.eclipse.jetty.io.Connection newConnection = sslConnectionFactory.newConnection(endPoint, context); Helper.replaceConnection(oldConnection, newConnection); LOG.debug("HTTP tunnel established: {} over {}", oldConnection, newConnection); } catch (Throwable x) { tunnelFailed(x); } } private void tunnelFailed(Throwable failure) { endPoint.close(); failed(failure); } } }