-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New request body API #10781
New request body API #10781
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sonar has some concerns with Null pointer exceptions
http/src/main/java/io/micronaut/http/exceptions/BufferLengthExceededException.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would like @dstepanov to review this when he is back
...-server-netty/src/main/java/io/micronaut/http/server/netty/HttpContentSubscriberFactory.java
Show resolved
Hide resolved
http-server-netty/src/main/java/io/micronaut/http/server/netty/body/BufferConsumer.java
Show resolved
Hide resolved
http-server-netty/src/main/java/io/micronaut/http/server/netty/body/NettyByteBody.java
Outdated
Show resolved
Hide resolved
http-server-netty/src/main/java/io/micronaut/http/server/netty/body/StreamingNettyByteBody.java
Show resolved
Hide resolved
http-server-netty/src/main/java/io/micronaut/http/server/netty/body/StreamingNettyByteBody.java
Show resolved
Hide resolved
|
||
While `ServerHttpRequest.byteBody()` returns a normal `ByteBody` -- cleanup is done by the HTTP server if the | ||
body is not consumed--the body returned by `split` is a `CloseableByteBody`. The caller *must* ensure that the | ||
new instance is closed, otherwise there can be resource and memory leaks, stalled connections, or other issues. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we need a documented example of this use case and splitting
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the BodyLogFilter has splitting
* `SplitBackpressureMode.NEW` uses the backpressure of the new consumer (the one `split()` returns) | ||
|
||
The argument-less `split()` method uses `SLOWEST`, but you should pick the mode that is most appropriate for your use | ||
case. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
again documentation needs example code
.onErrorComplete(ByteBody.BodyDiscardedException.class) // <7> | ||
.subscribe(array -> LOG.info("Received body: {}", Base64.getEncoder().encodeToString(array))); // <8> | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Provide example of how to do this in the blocking case
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This cant really be implemented in a blocking way, because that would block the filter until the whole body is received, which we dont want for logging
overall looks like a good change but think it needs some refinement to docs and clarification on blocking use cases. |
Co-authored-by: Tim Yates <tim.yates@gmail.com>
can you address the 6 new bugs identified by sonar |
16:29:30.708 [default-nioEventLoopGroup-1-3] INFO i.m.docs.server.body.BodyLogFilter - Received body: ... | ||
---- | ||
|
||
Note that the logging in the above example is asynchronous, so the log statements may be interleaved as shown. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there a way to do Flux.toList()
to avoid this interleaving?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no, i simply do not want to make guarantees about ordering. in fact the sentence and the example log are lying, there is no interleaving atm, but i dont want people to rely on that.
all subscribers to the same body have equal priority and may get parts of the body in any order.
"claim" the `byteBody()` and e.g. parse the JSON. Finally, the body is closed at the end of the request lifecycle, | ||
discarding any data if it has not been claimed by the argument binder. | ||
|
||
=== Primary operations |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
split in multiple files
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should create
httpServer/byteBody/byteBodyPrimaryOperations.adoc
httpServer/byteBody/byteBodySplitting.adoc
httpServer/byteBody/byteBodyBackpressure.adoc
httpServer/byteBody/byteBodyDiscarding.adoc
httpServer/byteBody/byteBodyExample.adoc
httpServer/byteBody/byteBodyExample.adoc
@@ -707,25 +658,25 @@ public long getContentLength() { | |||
|
|||
@Override | |||
public boolean isFull() { | |||
return byteBody() instanceof ImmediateByteBody; | |||
return byteBody() instanceof AvailableNettyByteBody; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we deprecate this and the contents
methods and move them to the body interface?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the entire FullHttpRequest is now deprecated.
import io.micronaut.core.execution.CompletableFutureExecutionFlow; | ||
import io.micronaut.core.execution.ExecutionFlow; | ||
import io.micronaut.core.io.buffer.ByteBuffer; | ||
import org.jetbrains.annotations.Contract; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we use jetbrains annotations
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's compile only so it shouldnt hurt
* performed on it | ||
*/ | ||
@NonNull | ||
CloseableByteBody split(@NonNull SplitBackpressureMode backpressureMode); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand the naming here. Why split
? Should it be a copy with buffered data? So maybe buffered
or something else? WDYT @graemerocher
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it is not necessarily buffered. it splits the stream so that there can be two consumers
*/ | ||
@Contract("-> this") | ||
@NonNull | ||
default ByteBody allowDiscard() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it affect the current instance? The name sounds more like a setter. I would name it discarded
if only the returned instance is affected.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it affects the current instance
* @return The streamed bytes | ||
*/ | ||
@NonNull | ||
InputStream toInputStream(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should allow only using the input stream in lambda, which it will close: R read(is -> ...)
. And perhaps it would be user-friendly to have API like R readAndDiscard(is -> ...)
+ R readAndRetain(is -> ...)
to identify directly what should happen to the data. WDYT @graemerocher
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
those seem complementary and useful but there will be cases where the user needs to be in control when the input stream is closed I think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what would be the difference between discard and retain?
* @return The streamed bytes | ||
*/ | ||
@NonNull | ||
Publisher<byte[]> toByteArrayPublisher(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe these two should be moved to some StreamingByteBody
interface?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is intentional that ImmediateByteBody extends the streaming case, so that users write their code for the streaming case but we can give optimized implementations where possible.
I am not a fan of the naming I will look more on Thursday when I'm back. |
It is named ByteBody because it specifically deals with bytes. It is independent of HttpRequest.getBody which can also have independent, unserialized beans |
@yawkat can you rebase? |
ive merged 4.5.x into this, we can squash on PR merge |
Quality Gate passedIssues Measures |
This PR adds a new public API for accessing the bytes of a request in the HTTP server. While it shares some naming with the ByteBody API we used before, it is a very different design. It's designed to be public API, not netty-specific, but still powerful enough to allow for the ByteBody optimizations we had before.
At the moment, the new api is only used for requests and only on the server. I'd like to expand it to the response and to the client if possible. But there are some challenges, so it won't make it into this PR:
Another missing piece is a non-netty implementation of ByteBody. I am particularly interested in a servlet implementation based on InputStream. I will implement that as a separate PR.
Some pieces of the old netty-only body api remain (the ObjectBody impls) to keep changes down. Also AbstractHttpContentProcessor is finally removed, all fields are folded into FormDataHttpContentProcessor.