/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.undertow.protocols.ajp; import io.undertow.UndertowLogger; import io.undertow.UndertowMessages; import io.undertow.connector.ByteBufferPool; import io.undertow.connector.PooledByteBuffer; import io.undertow.server.protocol.framed.AbstractFramedChannel; import io.undertow.server.protocol.framed.AbstractFramedStreamSourceChannel; import io.undertow.server.protocol.framed.FrameHeaderData; import io.undertow.util.Attachable; import io.undertow.util.HeaderMap; import io.undertow.util.HttpString; import org.xnio.ChannelListener; import org.xnio.IoUtils; import org.xnio.OptionMap; import org.xnio.StreamConnection; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import static io.undertow.protocols.ajp.AjpConstants.FRAME_TYPE_END_RESPONSE; import static io.undertow.protocols.ajp.AjpConstants.FRAME_TYPE_REQUEST_BODY_CHUNK; import static io.undertow.protocols.ajp.AjpConstants.FRAME_TYPE_SEND_BODY_CHUNK; import static io.undertow.protocols.ajp.AjpConstants.FRAME_TYPE_SEND_HEADERS; /** * AJP client side channel. * * @author Stuart Douglas */ public class AjpClientChannel extends AbstractFramedChannel<AjpClientChannel, AbstractAjpClientStreamSourceChannel, AbstractAjpClientStreamSinkChannel> { private final AjpResponseParser ajpParser; private AjpClientResponseStreamSourceChannel source; private AjpClientRequestClientStreamSinkChannel sink; boolean sinkDone = true; boolean sourceDone = true; private boolean lastFrameSent; private boolean lastFrameRecieved; /** * Create a new {@link io.undertow.server.protocol.framed.AbstractFramedChannel} * 8 * * @param connectedStreamChannel The {@link org.xnio.channels.ConnectedStreamChannel} over which the WebSocket Frames should get send and received. * Be aware that it already must be "upgraded". * @param bufferPool The {@link org.xnio.Pool} which will be used to acquire {@link java.nio.ByteBuffer}'s from. */ public AjpClientChannel(StreamConnection connectedStreamChannel, ByteBufferPool bufferPool, OptionMap settings) { super(connectedStreamChannel, bufferPool, AjpClientFramePriority.INSTANCE, null, settings); ajpParser = new AjpResponseParser(); } @Override protected AbstractAjpClientStreamSourceChannel createChannel(FrameHeaderData frameHeaderData, PooledByteBuffer frameData) throws IOException { if (frameHeaderData instanceof SendHeadersResponse) { SendHeadersResponse h = (SendHeadersResponse) frameHeaderData; AjpClientResponseStreamSourceChannel sourceChannel = new AjpClientResponseStreamSourceChannel(this, h.headers, h.statusCode, h.reasonPhrase, frameData, (int) frameHeaderData.getFrameLength()); this.source = sourceChannel; return sourceChannel; } else if (frameHeaderData instanceof RequestBodyChunk) { RequestBodyChunk r = (RequestBodyChunk) frameHeaderData; this.sink.chunkRequested(r.getLength()); frameData.close(); return null; } else { frameData.close(); throw new RuntimeException("TODO: unknown frame"); } } @Override protected FrameHeaderData parseFrame(ByteBuffer data) throws IOException { ajpParser.parse(data); if (ajpParser.isComplete()) { try { AjpResponseParser parser = ajpParser; if (parser.prefix == FRAME_TYPE_SEND_HEADERS) { return new SendHeadersResponse(parser.statusCode, parser.reasonPhrase, parser.headers); } else if (parser.prefix == FRAME_TYPE_REQUEST_BODY_CHUNK) { return new RequestBodyChunk(parser.readBodyChunkSize); } else if (parser.prefix == FRAME_TYPE_SEND_BODY_CHUNK) { return new SendBodyChunk(parser.currentIntegerPart + 1); //+1 for the null terminator } else if (parser.prefix == FRAME_TYPE_END_RESPONSE) { boolean persistent = parser.currentIntegerPart != 0; if (!persistent) { lastFrameRecieved = true; lastFrameSent = true; } return new EndResponse(); } else { //TODO: ping pong ETC UndertowLogger.ROOT_LOGGER.debug("UNKOWN FRAME"); } } finally { ajpParser.reset(); } } return null; } public AjpClientRequestClientStreamSinkChannel sendRequest(final HttpString method, final String path, final HttpString protocol, final HeaderMap headers, final Attachable attachable, ChannelListener<AjpClientRequestClientStreamSinkChannel> finishListener) { if (!sinkDone || !sourceDone) { throw UndertowMessages.MESSAGES.ajpRequestAlreadyInProgress(); } sinkDone = false; sourceDone = false; AjpClientRequestClientStreamSinkChannel ajpClientRequestStreamSinkChannel = new AjpClientRequestClientStreamSinkChannel(this, finishListener, headers, path, method, protocol, attachable); sink = ajpClientRequestStreamSinkChannel; source = null; return ajpClientRequestStreamSinkChannel; } @Override protected boolean isLastFrameReceived() { return lastFrameRecieved; } @Override protected boolean isLastFrameSent() { return lastFrameSent; } protected void lastDataRead() { if(!lastFrameSent) { markReadsBroken(new ClosedChannelException()); markWritesBroken(new ClosedChannelException()); } lastFrameRecieved = true; lastFrameSent = true; IoUtils.safeClose(this); } @Override protected void handleBrokenSourceChannel(Throwable e) { IoUtils.safeClose(source, sink); UndertowLogger.REQUEST_IO_LOGGER.ioException(new IOException(e)); IoUtils.safeClose(this); } @Override protected void handleBrokenSinkChannel(Throwable e) { IoUtils.safeClose(source, sink); UndertowLogger.REQUEST_IO_LOGGER.ioException(new IOException(e)); IoUtils.safeClose(this); } @Override protected void closeSubChannels() { IoUtils.safeClose(source, sink); } protected OptionMap getSettings() { return super.getSettings(); } void sinkDone() { sinkDone = true; if (sourceDone) { sink = null; source = null; } } void sourceDone() { sourceDone = true; if (sinkDone) { sink = null; source = null; } else { sink.startDiscard(); } } @Override public boolean isOpen() { return super.isOpen() && !lastFrameSent && !lastFrameRecieved; } @Override protected synchronized void recalculateHeldFrames() throws IOException { super.recalculateHeldFrames(); } class SendHeadersResponse implements FrameHeaderData { private final int statusCode; private final String reasonPhrase; private final HeaderMap headers; SendHeadersResponse(int statusCode, String reasonPhrase, HeaderMap headers) { this.statusCode = statusCode; this.reasonPhrase = reasonPhrase; this.headers = headers; } @Override public long getFrameLength() { //zero return 0; } @Override public AbstractFramedStreamSourceChannel<?, ?, ?> getExistingChannel() { return null; } } class RequestBodyChunk implements FrameHeaderData { private final int length; RequestBodyChunk(int length) { this.length = length; } public int getLength() { return length; } @Override public long getFrameLength() { return 0; } @Override public AbstractFramedStreamSourceChannel<?, ?, ?> getExistingChannel() { return null; } } class SendBodyChunk implements FrameHeaderData { private final int length; SendBodyChunk(int length) { this.length = length; } @Override public long getFrameLength() { return length; } @Override public AbstractFramedStreamSourceChannel<?, ?, ?> getExistingChannel() { return source; } } class EndResponse implements FrameHeaderData { @Override public long getFrameLength() { return 0; } @Override public AbstractFramedStreamSourceChannel<?, ?, ?> getExistingChannel() { return source; } } }