Search in sources :

Example 26 with Publisher

use of org.reactivestreams.Publisher in project spring-framework by spring-projects.

the class DataBufferUtils method takeUntilByteCount.

/**
	 * Relay buffers from the given {@link Publisher} until the total
	 * {@linkplain DataBuffer#readableByteCount() byte count} reaches
	 * the given maximum byte count, or until the publisher is complete.
	 * @param publisher the publisher to filter
	 * @param maxByteCount the maximum byte count
	 * @return a flux whose maximum byte count is {@code maxByteCount}
	 */
public static Flux<DataBuffer> takeUntilByteCount(Publisher<DataBuffer> publisher, long maxByteCount) {
    Assert.notNull(publisher, "Publisher must not be null");
    Assert.isTrue(maxByteCount >= 0, "'maxByteCount' must be a positive number");
    AtomicLong byteCountDown = new AtomicLong(maxByteCount);
    return Flux.from(publisher).takeWhile(dataBuffer -> {
        int delta = -dataBuffer.readableByteCount();
        long currentCount = byteCountDown.getAndAdd(delta);
        return currentCount >= 0;
    }).map(dataBuffer -> {
        long currentCount = byteCountDown.get();
        if (currentCount >= 0) {
            return dataBuffer;
        } else {
            int size = (int) (currentCount + dataBuffer.readableByteCount());
            return dataBuffer.slice(0, size);
        }
    });
}
Also used : ReadableByteChannel(java.nio.channels.ReadableByteChannel) Channels(java.nio.channels.Channels) BiFunction(java.util.function.BiFunction) Publisher(org.reactivestreams.Publisher) FluxSink(reactor.core.publisher.FluxSink) CompletionHandler(java.nio.channels.CompletionHandler) IOException(java.io.IOException) AsynchronousFileChannel(java.nio.channels.AsynchronousFileChannel) ByteBuffer(java.nio.ByteBuffer) SynchronousSink(reactor.core.publisher.SynchronousSink) AtomicLong(java.util.concurrent.atomic.AtomicLong) Flux(reactor.core.publisher.Flux) InputStream(java.io.InputStream) Channel(java.nio.channels.Channel) Assert(org.springframework.util.Assert) AtomicLong(java.util.concurrent.atomic.AtomicLong)

Example 27 with Publisher

use of org.reactivestreams.Publisher in project spring-framework by spring-projects.

the class JsonObjectDecoder method decode.

@Override
public Flux<DataBuffer> decode(Publisher<DataBuffer> inputStream, ResolvableType elementType, MimeType mimeType, Map<String, Object> hints) {
    return Flux.from(inputStream).flatMap(new Function<DataBuffer, Publisher<? extends DataBuffer>>() {

        int openBraces;

        int index;

        int state;

        boolean insideString;

        ByteBuf input;

        Integer writerIndex;

        @Override
        public Publisher<? extends DataBuffer> apply(DataBuffer buffer) {
            List<DataBuffer> chunks = new ArrayList<>();
            if (this.input == null) {
                this.input = Unpooled.copiedBuffer(buffer.asByteBuffer());
                DataBufferUtils.release(buffer);
                this.writerIndex = this.input.writerIndex();
            } else {
                this.index = this.index - this.input.readerIndex();
                this.input = Unpooled.copiedBuffer(this.input, Unpooled.copiedBuffer(buffer.asByteBuffer()));
                DataBufferUtils.release(buffer);
                this.writerIndex = this.input.writerIndex();
            }
            if (this.state == ST_CORRUPTED) {
                this.input.skipBytes(this.input.readableBytes());
                return Flux.error(new IllegalStateException("Corrupted stream"));
            }
            if (this.writerIndex > maxObjectLength) {
                // buffer size exceeded maxObjectLength; discarding the complete buffer.
                this.input.skipBytes(this.input.readableBytes());
                reset();
                return Flux.error(new IllegalStateException("object length exceeds " + maxObjectLength + ": " + this.writerIndex + " bytes discarded"));
            }
            DataBufferFactory dataBufferFactory = buffer.factory();
            for (; /* use current index */
            this.index < this.writerIndex; this.index++) {
                byte c = this.input.getByte(this.index);
                if (this.state == ST_DECODING_NORMAL) {
                    decodeByte(c, this.input, this.index);
                    // that the JSON object/array is complete.
                    if (this.openBraces == 0) {
                        ByteBuf json = extractObject(this.input, this.input.readerIndex(), this.index + 1 - this.input.readerIndex());
                        if (json != null) {
                            chunks.add(dataBufferFactory.wrap(json.nioBuffer()));
                        }
                        // The JSON object/array was extracted => discard the bytes from
                        // the input buffer.
                        this.input.readerIndex(this.index + 1);
                        // Reset the object state to get ready for the next JSON object/text
                        // coming along the byte stream.
                        reset();
                    }
                } else if (this.state == ST_DECODING_ARRAY_STREAM) {
                    decodeByte(c, this.input, this.index);
                    if (!this.insideString && (this.openBraces == 1 && c == ',' || this.openBraces == 0 && c == ']')) {
                        // because the byte at position index is not a whitespace.
                        for (int i = this.input.readerIndex(); Character.isWhitespace(this.input.getByte(i)); i++) {
                            this.input.skipBytes(1);
                        }
                        // skip trailing spaces.
                        int idxNoSpaces = this.index - 1;
                        while (idxNoSpaces >= this.input.readerIndex() && Character.isWhitespace(this.input.getByte(idxNoSpaces))) {
                            idxNoSpaces--;
                        }
                        ByteBuf json = extractObject(this.input, this.input.readerIndex(), idxNoSpaces + 1 - this.input.readerIndex());
                        if (json != null) {
                            chunks.add(dataBufferFactory.wrap(json.nioBuffer()));
                        }
                        this.input.readerIndex(this.index + 1);
                        if (c == ']') {
                            reset();
                        }
                    }
                // JSON object/array detected. Accumulate bytes until all braces/brackets are closed.
                } else if (c == '{' || c == '[') {
                    initDecoding(c, streamArrayElements);
                    if (this.state == ST_DECODING_ARRAY_STREAM) {
                        // Discard the array bracket
                        this.input.skipBytes(1);
                    }
                // Discard leading spaces in front of a JSON object/array.
                } else if (Character.isWhitespace(c)) {
                    this.input.skipBytes(1);
                } else {
                    this.state = ST_CORRUPTED;
                    return Flux.error(new IllegalStateException("invalid JSON received at byte position " + this.index + ": " + ByteBufUtil.hexDump(this.input)));
                }
            }
            return Flux.fromIterable(chunks);
        }

        /**
			 * Override this method if you want to filter the json objects/arrays that
			 * get passed through the pipeline.
			 */
        protected ByteBuf extractObject(ByteBuf buffer, int index, int length) {
            return buffer.slice(index, length).retain();
        }

        private void decodeByte(byte c, ByteBuf input, int index) {
            if ((c == '{' || c == '[') && !this.insideString) {
                this.openBraces++;
            } else if ((c == '}' || c == ']') && !this.insideString) {
                this.openBraces--;
            } else if (c == '"') {
                // also contain braces/brackets and that could lead to incorrect results.
                if (!this.insideString) {
                    this.insideString = true;
                // If the double quote wasn't escaped then this is the end of a string.
                } else if (input.getByte(index - 1) != '\\') {
                    this.insideString = false;
                }
            }
        }

        private void initDecoding(byte openingBrace, boolean streamArrayElements) {
            this.openBraces = 1;
            if (openingBrace == '[' && streamArrayElements) {
                this.state = ST_DECODING_ARRAY_STREAM;
            } else {
                this.state = ST_DECODING_NORMAL;
            }
        }

        private void reset() {
            this.insideString = false;
            this.state = ST_INIT;
            this.openBraces = 0;
        }
    });
}
Also used : Publisher(org.reactivestreams.Publisher) ByteBuf(io.netty.buffer.ByteBuf) ArrayList(java.util.ArrayList) List(java.util.List) DataBufferFactory(org.springframework.core.io.buffer.DataBufferFactory) DataBuffer(org.springframework.core.io.buffer.DataBuffer)

Example 28 with Publisher

use of org.reactivestreams.Publisher in project spring-framework by spring-projects.

the class FormHttpMessageWriter method write.

@Override
public Mono<Void> write(Publisher<? extends MultiValueMap<String, String>> inputStream, ResolvableType elementType, MediaType mediaType, ReactiveHttpOutputMessage outputMessage, Map<String, Object> hints) {
    MediaType contentType = outputMessage.getHeaders().getContentType();
    if (contentType == null) {
        contentType = MediaType.APPLICATION_FORM_URLENCODED;
        outputMessage.getHeaders().setContentType(contentType);
    }
    Charset charset = getMediaTypeCharset(contentType);
    return Flux.from(inputStream).single().map(form -> generateForm(form, charset)).then(value -> {
        ByteBuffer byteBuffer = charset.encode(value);
        DataBuffer buffer = outputMessage.bufferFactory().wrap(byteBuffer);
        outputMessage.getHeaders().setContentLength(byteBuffer.remaining());
        return outputMessage.writeWith(Mono.just(buffer));
    });
}
Also used : Iterator(java.util.Iterator) Publisher(org.reactivestreams.Publisher) MediaType(org.springframework.http.MediaType) MultiValueMap(org.springframework.util.MultiValueMap) Mono(reactor.core.publisher.Mono) DataBuffer(org.springframework.core.io.buffer.DataBuffer) ByteBuffer(java.nio.ByteBuffer) StandardCharsets(java.nio.charset.StandardCharsets) ReactiveHttpOutputMessage(org.springframework.http.ReactiveHttpOutputMessage) Flux(reactor.core.publisher.Flux) URLEncoder(java.net.URLEncoder) List(java.util.List) Charset(java.nio.charset.Charset) Map(java.util.Map) ResolvableType(org.springframework.core.ResolvableType) UnsupportedEncodingException(java.io.UnsupportedEncodingException) Collections(java.util.Collections) Assert(org.springframework.util.Assert) MediaType(org.springframework.http.MediaType) Charset(java.nio.charset.Charset) ByteBuffer(java.nio.ByteBuffer) DataBuffer(org.springframework.core.io.buffer.DataBuffer)

Aggregations

Publisher (org.reactivestreams.Publisher)28 List (java.util.List)11 Mono (reactor.core.publisher.Mono)11 Test (org.junit.Test)9 Collections (java.util.Collections)8 Assert.assertSame (org.junit.Assert.assertSame)7 CharSequenceEncoder (org.springframework.core.codec.CharSequenceEncoder)7 HttpStatus (org.springframework.http.HttpStatus)7 EncoderHttpMessageWriter (org.springframework.http.codec.EncoderHttpMessageWriter)7 MockServerHttpRequest (org.springframework.mock.http.server.reactive.test.MockServerHttpRequest)7 Duration (java.time.Duration)6 CoreMatchers.instanceOf (org.hamcrest.CoreMatchers.instanceOf)6 CoreMatchers.startsWith (org.hamcrest.CoreMatchers.startsWith)6 Matchers.is (org.hamcrest.Matchers.is)6 Assert.assertEquals (org.junit.Assert.assertEquals)6 Assert.assertThat (org.junit.Assert.assertThat)6 Before (org.junit.Before)6 Subscriber (org.reactivestreams.Subscriber)6 AnnotationConfigApplicationContext (org.springframework.context.annotation.AnnotationConfigApplicationContext)6 Bean (org.springframework.context.annotation.Bean)6