Add HttpResponseBuilder to fluently build HttpResponse#3941
Add HttpResponseBuilder to fluently build HttpResponse#3941minwoox merged 30 commits intoline:masterfrom
HttpResponse#3941Conversation
| /** | ||
| * Sets the content as UTF_8 for this response. | ||
| */ | ||
| public HttpResponseBuilder content(String content) { | ||
| requireNonNull(content, "content"); | ||
| return content(MediaType.PLAIN_TEXT_UTF_8, content); | ||
| } | ||
|
|
||
| /** | ||
| * Sets the content for this response. | ||
| */ | ||
| public HttpResponseBuilder content(MediaType contentType, CharSequence content) { | ||
| requireNonNull(contentType, "contentType"); | ||
| requireNonNull(content, "content"); | ||
| return content(contentType, HttpData.of(contentType.charset(StandardCharsets.UTF_8), content)); | ||
| } | ||
|
|
||
| /** | ||
| * Sets the content for this response. | ||
| */ | ||
| public HttpResponseBuilder content(MediaType contentType, String content) { | ||
| requireNonNull(contentType, "contentType"); | ||
| requireNonNull(content, "content"); | ||
| return content(contentType, HttpData.of(contentType.charset(StandardCharsets.UTF_8), content)); | ||
| } |
There was a problem hiding this comment.
We may extract the common part between AbstractHttpRequestBuilder and HttpResponseBuilder to AbstractHttpMessageBuilder.
There was a problem hiding this comment.
Would be useful if we add contentJson(Object content)?
There was a problem hiding this comment.
We may extract the common part between AbstractHttpRequestBuilder and HttpResponseBuilder to AbstractHttpMessageBuilder.
Should I address it in this PR?
Would be useful if we add contentJson(Object content)?
It seems to be useful.
I will add contentJson(Object content).
There was a problem hiding this comment.
How about also adding the content method that takes Publisher as an argument?
public HttpResponseBuilder content(Publisher<? extends HttpData> content) {...}
There was a problem hiding this comment.
Should I address it in this PR?
I hope so. 😀 Because duplicate code is caused by this new feature.
There was a problem hiding this comment.
How about also adding the content method that takes Publisher as an argument?
It seems to be nice.
…ich is converted into JSON by ObjectMapper
…sponseBuilder to AbstractHttpMessageBuilder.
Codecov Report
@@ Coverage Diff @@
## master #3941 +/- ##
============================================
+ Coverage 73.25% 73.28% +0.03%
- Complexity 15646 15691 +45
============================================
Files 1368 1370 +2
Lines 60025 60102 +77
Branches 7621 7625 +4
============================================
+ Hits 43972 44047 +75
+ Misses 12200 12196 -4
- Partials 3853 3859 +6
Continue to review full report at Codecov.
|
minwoox
left a comment
There was a problem hiding this comment.
Basically, looks good! 😄
| */ | ||
| public HttpResponseBuilder headers(Iterable<? extends Entry<? extends CharSequence, String>> headers) { | ||
| requireNonNull(headers, "headers"); | ||
| responseHeadersBuilder.set(headers); |
There was a problem hiding this comment.
I think it is a bug to use set here. #3932 (comment)
Could you use add for header and headers instead.
minwoox
left a comment
There was a problem hiding this comment.
Thanks! I think we are almost there. 😄
| @SuppressWarnings("checkstyle:OverloadMethodsDeclarationOrder") | ||
| abstract class AbstractHttpMessageBuilder { | ||
|
|
||
| private final HttpHeadersBuilder httpHeaders = HttpHeaders.builder(); |
There was a problem hiding this comment.
How about removing this and adding/setting directly to RequestHeadersBuilder and ResponseHeadersBuilder?
abstract class AbstractHttpMessageBuilder {
abstract HttpHeadersBuilder headersBuilder();
AbstractHttpMessageBuilder header(CharSequence name, Object value) {
headersBuilder().addObject(requireNonNull(name, "name"),
requireNonNull(value, "value"));
return this;
}
}
public abstract class AbstractHttpRequestBuilder extends AbstractHttpMessageBuilder {
private final RequestHeadersBuilder requestHeadersBuilder = RequestHeaders.builder();
@override
HttpHeadersBuilder headersBuilder() {
return requestHeadersBuilder;
}
}
public final class HttpResponseBuilder extends AbstractHttpMessageBuilder {
private final ResponseHeadersBuilder responseHeadersBuilder = ResponseHeaders.builder();
@override
HttpHeadersBuilder headersBuilder() {
return responseHeadersBuilder;
}
}| @Nullable | ||
| private HttpHeadersBuilder httpTrailers; | ||
|
|
||
| protected final HttpHeadersBuilder httpHeaders() { |
There was a problem hiding this comment.
I think we can remove all protected and public modifiers in this class.
| if (httpTrailers == null) { | ||
| httpTrailers = HttpHeaders.builder(); | ||
| } | ||
| httpTrailers.set(trailers); |
There was a problem hiding this comment.
| httpTrailers.set(trailers); | |
| httpTrailers.add(trailers); |
| return HttpResponse.of(responseHeaders, content, trailers.build()); | ||
| } | ||
| } else { | ||
| return HttpResponse.of(responseHeaders, publisher); |
There was a problem hiding this comment.
I think we should also contain trailers if it's not null as we do in the request builder.
minwoox
left a comment
There was a problem hiding this comment.
I guess this is the final round. 😄
|
Added |
ikhoon
left a comment
There was a problem hiding this comment.
Almost there! Left only minor nits.
jrhee17
left a comment
There was a problem hiding this comment.
The changes look really nice 👍 LGTM apart from minor comments. Thank you! 🙇
| } | ||
|
|
||
| AbstractHttpMessageBuilder contentJson(Object content) { | ||
| requireNonNull(content, "content"); |
There was a problem hiding this comment.
nit; should we also reject setting the content if publisher has been set already?
Otherwise, a user may set both contentJson and publisher, and it will be ambiguous which content will be applied.
checkState(publisher == null, "publisher has been set already");
There was a problem hiding this comment.
or we can also modify to reuse content(MediaType contentType, HttpData content)
i.e.
AbstractHttpMessageBuilder contentJson(Object content) {
requireNonNull(content, "content");
try {
return content(MediaType.JSON, HttpData.wrap(JacksonUtil.writeValueAsBytes(content)));
} catch (JsonProcessingException e) {
throw new IllegalArgumentException("failed to serialize " + content, e);
}
}
| * Sets 200 OK to the status of this response. | ||
| */ | ||
| public HttpResponseBuilder ok() { | ||
| status(HttpStatus.OK); |
There was a problem hiding this comment.
nit+optional; could just return as-is
| status(HttpStatus.OK); | |
| return status(HttpStatus.OK); |
| package com.linecorp.armeria.common; | ||
|
|
||
| import static org.assertj.core.api.Assertions.assertThatThrownBy; | ||
| import static org.assertj.core.api.AssertionsForClassTypes.assertThat; |
There was a problem hiding this comment.
Prefer to use import static org.assertj.core.api.Assertions.assertThat; so that we can take full advantage of the assertj API
Co-authored-by: jrhee17 <[email protected]>
Co-authored-by: jrhee17 <[email protected]>
Co-authored-by: jrhee17 <[email protected]>
Co-authored-by: jrhee17 <[email protected]>
ikhoon
left a comment
There was a problem hiding this comment.
Looking good. Left only minor nits.
| } | ||
|
|
||
| /** | ||
| * Sets a header for this response. For example: |
| } | ||
|
|
||
| /** | ||
| * Sets multiple headers for this response. For example: |
Had a chat internally, my previous comment does not comply with Armeria's policy. |
| AbstractHttpMessageBuilder contentJson(Object content) { | ||
| requireNonNull(content, "content"); | ||
| checkState(publisher == null, "publisher has been set already"); | ||
| headersBuilder().contentType(MediaType.JSON); |
There was a problem hiding this comment.
Could be removed now?
| headersBuilder().contentType(MediaType.JSON); |
| StreamMessage.concat(publisher, StreamMessage.of(httpTrailers.build()))); | ||
| } | ||
| } | ||
| final HttpData content = content(); |
There was a problem hiding this comment.
Though, it is not related to this PR, could we delegate the following code to HttpRequest.of()?
HttpData content = firstNonNull(content, HttpData.empty());
HttpHeaders httpTrailers;
if (httpTrailersBuiler != null) {
httpTrailers = httpTrailersBuiler.build();
} else {
httpTrailers = HttpHeaders.of();
}
return HttpRequest.of(requestHeaders, content, httpTrailers);|
Now I can fluently build my response. 🤣 |
…s)` (#4727) **Related Issue** #3959 ### Motivation: ``` We have PrependingPublisher to publish ResponseHeaders and body in a StreamMessage. Similarly, we can add AppendingPublisher to publish body and HTTP trailers more efficiently. ``` - On #3941, we added `PrependingPublisher` to publish `ResponseHeaders` and body in a `StreamMessage` - So it's nice to add `SurroundingPublisher` to publish body and `HTTP trailers` too! ### Modifications: - Add `SurroundingPublisher` to append trailers - Add `HttpResponse.of(header, pub, trailers)` that using `SurroundingPublisher` ### Result: - You can now publish body and `HTTP trailers` easily by using `SurroundingPublisher` - Close #3959 issue --------- Co-authored-by: Ikhun Um <[email protected]> Co-authored-by: Ikhun Um <[email protected]>
Motivation:
It would be convenient to add
HttpResponseBuilderas likeHttpRequestBuilderModifications:
HttpResponseBuilderto buildHttpResponseHttpResponse#builderto get a builder instanceResult:
HttpResponse#3398HttpResponseby using this builder as like HttpRequestBuilder