Ejemplo n.º 1
0
/**
 * 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));
  }
}
Ejemplo n.º 4
0
  @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());
  }
Ejemplo n.º 7
0
/** 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");
    }
  }
Ejemplo n.º 10
0
/**
 * {@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) {}
}
Ejemplo n.º 11
0
/** 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;
  }
}
Ejemplo n.º 14
0
/**
 * 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());
        }
      }
    }
  }
}
Ejemplo n.º 18
0
/**
 * 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();
    }
  }
}
Ejemplo n.º 19
0
@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));
  }
}
Ejemplo n.º 20
0
/**
 * {@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);
    }
  }
}
Ejemplo n.º 21
0
/**
 * 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;
    }
  }
}
Ejemplo n.º 24
0
@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);
  }
}
Ejemplo n.º 25
0
/* ------------------------------------------------------------ */
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());
  }
}
Ejemplo n.º 26
0
/**
 * 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());
  }
}
Ejemplo n.º 27
0
/**
 * {@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>
 * &lt;form method="POST" enctype="multipart/form-data" accept-charset="UTF-8"&gt;
 *     &lt;input type="text" name="field" value="foo" /&gt;
 *     &lt;input type="file" name="icon" /&gt;
 * &lt;/form&gt;
 * </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=&lt;charset_name&gt;"}, 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);
    }
  }
}
Ejemplo n.º 29
0
/**
 * 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;
  }
}
Ejemplo n.º 30
0
  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);
      }
    }
  }