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

io.micronaut.http.client.netty.SseSplitter Maven / Gradle / Ivy

/*
 * Copyright 2017-2024 original authors
 *
 * 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
 *
 * https://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.micronaut.http.client.netty;

import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.http.body.stream.BodySizeLimits;
import io.micronaut.http.client.exceptions.ContentLengthExceededException;
import io.netty.buffer.ByteBuf;
import reactor.core.publisher.Flux;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;

/**
 * Split buffers into lines for SSE parsing.
 *
 * @author Jonas Konrad
 * @since 4.7.0
 */
@Internal
final class SseSplitter {
    /**
     * Split a single buffer.
     *
     * @param buf The buffer
     * @return The individual split pieces
     */
    @NonNull
    static List split(@NonNull ByteBuf buf) {
        buf.touch();
        List split = new ArrayList<>();
        while (true) {
            int eol = findEndOfLine(buf);
            if (eol == -1) {
                break;
            }
            // todo: this is necessary because downstream handlers sometimes do the
            //  `if (refcnt > 0) release` pattern. We should eventually fix that.
            split.add(buf.readSlice(eol).copy());
            // skip newline
            if (buf.readByte() == '\r') {
                buf.skipBytes(1);
            }
        }
        split.add(buf);
        return split;
    }

    /**
     * Split a stream of bytes. Any content past the last newline is ignored.
     * (This matches legacy behavior)
     *
     * @param buf The input buffers
     * @param limits Buffer limits
     * @return The output buffers, split into lines
     */
    @NonNull
    static Flux split(@NonNull Flux buf, @NonNull BodySizeLimits limits) {
        AtomicReference last = new AtomicReference<>();
        return buf.concatMapIterable(bb -> {
            ByteBuf joined = last.get();
            if (joined == null) {
                joined = bb;
            } else {
                long combinedLength = (long) joined.readableBytes() + bb.readableBytes();
                if (combinedLength > limits.maxBufferSize()) {
                    bb.release();
                    throw new ContentLengthExceededException(limits.maxBufferSize(), combinedLength);
                }
                joined.writeBytes(bb);
            }
            List split = split(joined);
            ByteBuf l = split.get(split.size() - 1);
            // copy & release to avoid endless accumulation in the same buffer
            last.set(l.copy());
            l.release();
            return split.subList(0, split.size() - 1);
        })
            .doOnDiscard(ByteBuf.class, ByteBuf::release)
            .doOnTerminate(() -> {
                // last line is *not* emitted, same as LineBasedFrameDecoder. This line is normally
                // empty anyway
                ByteBuf l = last.get();
                if (l != null) {
                    last.set(null);
                    l.release();
                }
            });
    }

    private static int findEndOfLine(final ByteBuf buffer) {
        // adapted from netty LineBasedFrameDecoder
        int i = buffer.indexOf(buffer.readerIndex(), buffer.readerIndex() + buffer.readableBytes(), (byte) '\n');
        if (i >= 0) {
            if (i > 0 && buffer.getByte(i - 1) == '\r') {
                i--;
            }
            i -= buffer.readerIndex();
        }
        return i;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy