// // ======================================================================== // Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ // All rights reserved. This program and the accompanying materials // are made available under the terms of the Eclipse Public License v1.0 // and Apache License v2.0 which accompanies this distribution. // // The Eclipse Public License is available at // http://www.eclipse.org/legal/epl-v10.html // // The Apache License v2.0 is available at // http://www.opensource.org/licenses/apache2.0.php // // You may elect to redistribute this code under either of these licenses. // ======================================================================== // package org.eclipse.jetty.fcgi.server; import java.nio.ByteBuffer; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.eclipse.jetty.fcgi.FCGI; import org.eclipse.jetty.fcgi.generator.Flusher; import org.eclipse.jetty.fcgi.parser.ServerParser; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.io.AbstractConnection; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpInput; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; public class ServerFCGIConnection extends AbstractConnection { private static final Logger LOG = Log.getLogger(ServerFCGIConnection.class); private final ConcurrentMap<Integer, HttpChannelOverFCGI> channels = new ConcurrentHashMap<>(); private final Connector connector; private final boolean sendStatus200; private final Flusher flusher; private final HttpConfiguration configuration; private final ServerParser parser; public ServerFCGIConnection(Connector connector, EndPoint endPoint, HttpConfiguration configuration, boolean sendStatus200) { super(endPoint, connector.getExecutor()); this.connector = connector; this.flusher = new Flusher(endPoint); this.configuration = configuration; this.sendStatus200 = sendStatus200; this.parser = new ServerParser(new ServerListener()); } @Override public void onOpen() { super.onOpen(); fillInterested(); } @Override public void onFillable() { EndPoint endPoint = getEndPoint(); ByteBufferPool bufferPool = connector.getByteBufferPool(); ByteBuffer buffer = bufferPool.acquire(configuration.getResponseHeaderSize(), true); try { while (true) { int read = endPoint.fill(buffer); if (LOG.isDebugEnabled()) // Avoid boxing of variable 'read' LOG.debug("Read {} bytes from {}", read, endPoint); if (read > 0) { parse(buffer); } else if (read == 0) { bufferPool.release(buffer); fillInterested(); break; } else { bufferPool.release(buffer); shutdown(); break; } } } catch (Exception x) { if (LOG.isDebugEnabled()) LOG.debug(x); bufferPool.release(buffer); // TODO: fail and close ? } } private void parse(ByteBuffer buffer) { while (buffer.hasRemaining()) parser.parse(buffer); } private void shutdown() { flusher.shutdown(); } private class ServerListener implements ServerParser.Listener { @Override public void onStart(int request, FCGI.Role role, int flags) { // TODO: handle flags HttpChannelOverFCGI channel = new HttpChannelOverFCGI(connector, configuration, getEndPoint(), new HttpTransportOverFCGI(connector.getByteBufferPool(), flusher, request, sendStatus200)); HttpChannelOverFCGI existing = channels.putIfAbsent(request, channel); if (existing != null) throw new IllegalStateException(); if (LOG.isDebugEnabled()) LOG.debug("Request {} start on {}", request, channel); } @Override public void onHeader(int request, HttpField field) { HttpChannelOverFCGI channel = channels.get(request); if (LOG.isDebugEnabled()) LOG.debug("Request {} header {} on {}", request, field, channel); if (channel != null) channel.header(field); } @Override public void onHeaders(int request) { HttpChannelOverFCGI channel = channels.get(request); if (LOG.isDebugEnabled()) LOG.debug("Request {} headers on {}", request, channel); if (channel != null) { channel.onRequest(); channel.dispatch(); } } @Override public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) { HttpChannelOverFCGI channel = channels.get(request); if (LOG.isDebugEnabled()) LOG.debug("Request {} {} content {} on {}", request, stream, buffer, channel); if (channel != null) { ByteBuffer copy = ByteBuffer.allocate(buffer.remaining()); copy.put(buffer).flip(); channel.onContent(new HttpInput.Content(copy)); } return false; } @Override public void onEnd(int request) { HttpChannelOverFCGI channel = channels.remove(request); if (LOG.isDebugEnabled()) LOG.debug("Request {} end on {}", request, channel); if (channel != null) { channel.onContentComplete(); channel.onRequestComplete(); } } @Override public void onFailure(int request, Throwable failure) { HttpChannelOverFCGI channel = channels.remove(request); if (LOG.isDebugEnabled()) LOG.debug("Request {} failure on {}: {}", request, channel, failure); if (channel != null) { channel.onBadMessage(400, failure.toString()); } } } }