All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.firefly.codec.http2.stream.AbstractHTTP2OutputStream Maven / Gradle / Ivy

There is a newer version: 4.0.3.2
Show newest version
package com.firefly.codec.http2.stream;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.LinkedList;

import com.firefly.codec.http2.frame.DataFrame;
import com.firefly.codec.http2.frame.DisconnectFrame;
import com.firefly.codec.http2.frame.Frame;
import com.firefly.codec.http2.frame.HeadersFrame;
import com.firefly.codec.http2.model.HttpHeader;
import com.firefly.codec.http2.model.MetaData;
import com.firefly.utils.concurrent.Callback;
import com.firefly.utils.log.Log;
import com.firefly.utils.log.LogFactory;

abstract public class AbstractHTTP2OutputStream extends HTTPOutputStream {

	protected static final Log log = LogFactory.getInstance().getLog("firefly-system");

	protected boolean isChunked;
	private long size;
	private long contentLength;
	private boolean isWriting;
	private LinkedList frames = new LinkedList<>();
	private FrameCallback frameCallback = new FrameCallback();
	private DataFrame currentDataFrame;

	public AbstractHTTP2OutputStream(MetaData info, boolean clientMode) {
		super(info, clientMode);
	}

	@Override
	public synchronized void writeWithContentLength(ByteBuffer[] data) throws IOException {
		if (closed) {
			return;
		}

		try {
			if (!commited) {
				long contentLength = 0;
				for (ByteBuffer buf : data) {
					contentLength += buf.remaining();
				}
				info.getFields().put(HttpHeader.CONTENT_LENGTH, String.valueOf(contentLength));
			}

			for (ByteBuffer buf : data) {
				write(buf);
			}
		} finally {
			close();
		}
	}

	@Override
	public synchronized void writeWithContentLength(ByteBuffer data) throws IOException {
		if (closed) {
			return;
		}

		try {
			if (!commited) {
				info.getFields().put(HttpHeader.CONTENT_LENGTH, String.valueOf(data.remaining()));
			}

			write(data);
		} finally {
			close();
		}
	}

	@Override
	public void commit() throws IOException {
		commit(false);
	}

	@Override
	public void write(ByteBuffer data) throws IOException {
		if (closed)
			return;

		if (data == null || !data.hasRemaining())
			return;

		if (!commited) {
			commit(false);
		}

		boolean endStream = false;
		if (!isChunked) {
			size += data.remaining();
			log.debug("http2 output size: {}, content length: {}", size, contentLength);
			if (size >= contentLength) {
				endStream = true;
			}
		}

		final Stream stream = getStream();
		final DataFrame frame = new DataFrame(stream.getId(), data, endStream);
		writeFrame(frame);
	}

	public synchronized void writeFrame(Frame frame) {
		switch (frame.getType()) {
		case DATA:
			if (!commited)
				throw new IllegalStateException("the output stream is not commited");

			DataFrame dataFrame = (DataFrame) frame;
			if (isChunked) {
				if (dataFrame.isEndStream()) {
					if (currentDataFrame == null) {
						writeDataFrame(dataFrame);
					} else {
						writeDataFrame(currentDataFrame);
						writeDataFrame(dataFrame);
					}
				} else {
					if (currentDataFrame == null) {
						currentDataFrame = dataFrame;
					} else {
						writeDataFrame(currentDataFrame);
						currentDataFrame = dataFrame;
					}
				}
			} else {
				writeDataFrame(dataFrame);
			}
			break;
		case HEADERS:
			writeHeadersFrame((HeadersFrame) frame);
			break;
		case DISCONNECT:
			if (isChunked) {
				if (currentDataFrame != null) {
					if (!currentDataFrame.isEndStream()) {
						DataFrame theLastDataFrame = new DataFrame(currentDataFrame.getStreamId(),
								currentDataFrame.getData(), true);
						writeDataFrame(theLastDataFrame);
						currentDataFrame = null;
					} else {
						throw new IllegalStateException("the end data stream is cached");
					}
				} else {
					throw new IllegalStateException("the cached data stream is null");
				}
			} else {
				throw new IllegalArgumentException(
						"the frame type is error, only the chunked encoding can accept disconnect frame, current frame type is "
								+ frame.getType());
			}
			break;
		default:
			throw new IllegalArgumentException("the frame type is error, the type is " + frame.getType());
		}
	}

	protected synchronized void writeDataFrame(DataFrame dataFrame) {
		closed = dataFrame.isEndStream();

		if (isWriting) {
			frames.offer(dataFrame);
		} else {
			if (log.isDebugEnabled()) {
				log.debug("the stream {} writes a frame {}, remaining frames are {}", dataFrame.getStreamId(), dataFrame, frames.toString());
//				log.debug("the data content:\r\n {}", BufferUtils.toString(dataFrame.getData(), StandardCharsets.UTF_8));
			}

			isWriting = true;
			getStream().data(dataFrame, frameCallback);
		}
	}

	protected synchronized void writeHeadersFrame(HeadersFrame headersFrame) {
		closed = headersFrame.isEndStream();

		if (isWriting) {
			frames.offer(headersFrame);
		} else {
			if (log.isDebugEnabled()) {
				log.debug("the stream {} writes a frame {}", headersFrame.getStreamId(), headersFrame);
			}

			isWriting = true;
			getStream().headers(headersFrame, frameCallback);
		}
	}

	@Override
	public synchronized void close() throws IOException {
		if (closed)
			return;

		log.debug("http2 output stream is closing");
		if (!commited) {
			commit(true);
		} else {
			if (isChunked) {
				log.debug("output the last data frame to end stream");
				writeFrame(new DisconnectFrame());
			} else {
				closed = true;
			}
		}
	}

	protected synchronized void commit(final boolean endStream) throws IOException {
		if (closed)
			return;

		if (commited)
			return;

		// does use chunked encoding or content length ?
		contentLength = info.getFields().getLongField(HttpHeader.CONTENT_LENGTH.asString());
		if (endStream) {
			if (log.isDebugEnabled()) {
				log.debug("stream {} commits header and closes it", getStream().getId());
			}
			isChunked = false;
		} else {
			isChunked = (contentLength <= 0);
		}

		if (log.isDebugEnabled()) {
			log.debug("is stream {} using chunked encoding ? {}", getStream().getId(), isChunked);
		}
		
		info.getFields().put(HttpHeader.X_POWERED_BY, "Firefly 4.0");
		info.getFields().put(HttpHeader.SERVER, "Firefly 4.0");
		
		final Stream stream = getStream();
		final HeadersFrame headersFrame = new HeadersFrame(stream.getId(), info, null, endStream);
		if (log.isDebugEnabled()) {
			log.debug("stream {} commits the header frame {}", stream.getId(), headersFrame);
		}

		commited = true;
		writeFrame(headersFrame);
	}

	private class FrameCallback implements Callback {

		@Override
		public void succeeded() {
			synchronized (AbstractHTTP2OutputStream.this) {
				isWriting = false;
				final Frame frame = frames.poll();
				if (frame != null) {
					switch (frame.getType()) {
					case DATA:
						writeDataFrame((DataFrame) frame);
						break;
					case HEADERS:
						writeHeadersFrame((HeadersFrame) frame);
						break;
					default:
						throw new IllegalArgumentException("the frame type is error, the type is " + frame.getType());
					}
				} else {
					isWriting = false;
				}

				if (log.isDebugEnabled()) {
					log.debug("the stream {} outputs http2 frame successfully, and the queue size is {}",
							getStream().getId(), frames.size());
				}
			}
		}

		@Override
		public void failed(Throwable x) {
			synchronized (AbstractHTTP2OutputStream.this) {
				log.error("the stream {} outputs http2 frame unsuccessfully ", x, getStream().getId());
				isWriting = false;
			}
		}

		@Override
		public boolean isNonBlocking() {
			return false;
		}

	}

	abstract protected Stream getStream();
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy