Skip to content

Add HttpResponseBuilder to fluently build HttpResponse#3941

Merged
minwoox merged 30 commits intoline:masterfrom
ta7uw:issue-3398
Dec 13, 2021
Merged

Add HttpResponseBuilder to fluently build HttpResponse#3941
minwoox merged 30 commits intoline:masterfrom
ta7uw:issue-3398

Conversation

@ta7uw
Copy link
Copy Markdown
Member

@ta7uw ta7uw commented Nov 22, 2021

Motivation:

It would be convenient to add HttpResponseBuilder as like HttpRequestBuilder

Modifications:

  • Add HttpResponseBuilder to build HttpResponse
  • Add HttpResponse#builder to get a builder instance

Result:

HttpResponse.builder()
            .ok()
            .content(content)
            .build(); 

Comment on lines +128 to +152
/**
* 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));
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may extract the common part between AbstractHttpRequestBuilder and HttpResponseBuilder to AbstractHttpMessageBuilder.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be useful if we add contentJson(Object content)?

Copy link
Copy Markdown
Member Author

@ta7uw ta7uw Nov 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about also adding the content method that takes Publisher as an argument?

public HttpResponseBuilder content(Publisher<? extends HttpData> content) {...}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I address it in this PR?

I hope so. 😀 Because duplicate code is caused by this new feature.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about also adding the content method that takes Publisher as an argument?

It seems to be nice.

@codecov
Copy link
Copy Markdown

codecov Bot commented Nov 24, 2021

Codecov Report

Merging #3941 (e979549) into master (3ec1e4d) will increase coverage by 0.03%.
The diff coverage is 75.80%.

Impacted file tree graph

@@             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     
Impacted Files Coverage Δ
...rp/armeria/client/WebClientRequestPreparation.java 65.21% <0.00%> (-2.97%) ⬇️
...om/linecorp/armeria/common/HttpRequestBuilder.java 87.09% <0.00%> (-9.34%) ⬇️
...m/linecorp/armeria/common/HttpResponseBuilder.java 76.74% <76.74%> (ø)
...orp/armeria/common/AbstractHttpMessageBuilder.java 78.72% <78.72%> (ø)
...orp/armeria/common/AbstractHttpRequestBuilder.java 91.47% <85.18%> (-0.36%) ⬇️
...java/com/linecorp/armeria/common/HttpResponse.java 91.45% <100.00%> (+0.04%) ⬆️
.../linecorp/armeria/client/DefaultClientFactory.java 72.91% <0.00%> (-2.09%) ⬇️
.../com/linecorp/armeria/server/docs/ServiceInfo.java 78.94% <0.00%> (-1.76%) ⬇️
...rp/armeria/common/stream/DefaultStreamMessage.java 86.55% <0.00%> (-1.08%) ⬇️
...armeria/client/HttpClientPipelineConfigurator.java 77.19% <0.00%> (-0.98%) ⬇️
... and 12 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 3ec1e4d...e979549. Read the comment docs.

Comment thread core/src/main/java/com/linecorp/armeria/common/AbstractHttpMessageBuilder.java Outdated
Comment thread core/src/main/java/com/linecorp/armeria/common/AbstractHttpMessageBuilder.java Outdated
Comment thread core/src/main/java/com/linecorp/armeria/common/AbstractHttpMessageBuilder.java Outdated
Copy link
Copy Markdown
Contributor

@minwoox minwoox left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically, looks good! 😄

Comment thread core/src/main/java/com/linecorp/armeria/common/AbstractHttpMessageBuilder.java Outdated
Comment thread core/src/main/java/com/linecorp/armeria/common/AbstractHttpRequestBuilder.java Outdated
Comment thread core/src/main/java/com/linecorp/armeria/common/AbstractHttpRequestBuilder.java Outdated
*/
public HttpResponseBuilder headers(Iterable<? extends Entry<? extends CharSequence, String>> headers) {
requireNonNull(headers, "headers");
responseHeadersBuilder.set(headers);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is a bug to use set here. #3932 (comment)
Could you use add for header and headers instead.

Copy link
Copy Markdown
Contributor

@minwoox minwoox left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I think we are almost there. 😄

@SuppressWarnings("checkstyle:OverloadMethodsDeclarationOrder")
abstract class AbstractHttpMessageBuilder {

private final HttpHeadersBuilder httpHeaders = HttpHeaders.builder();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;
    }
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems good to me.

@Nullable
private HttpHeadersBuilder httpTrailers;

protected final HttpHeadersBuilder httpHeaders() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can remove all protected and public modifiers in this class.

if (httpTrailers == null) {
httpTrailers = HttpHeaders.builder();
}
httpTrailers.set(trailers);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread core/src/main/java/com/linecorp/armeria/common/AbstractHttpRequestBuilder.java Outdated
return HttpResponse.of(responseHeaders, content, trailers.build());
}
} else {
return HttpResponse.of(responseHeaders, publisher);
Copy link
Copy Markdown
Contributor

@minwoox minwoox Nov 29, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should also contain trailers if it's not null as we do in the request builder.

Copy link
Copy Markdown
Contributor

@minwoox minwoox left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this is the final round. 😄

Comment thread core/src/main/java/com/linecorp/armeria/common/AbstractHttpMessageBuilder.java Outdated
Comment thread core/src/main/java/com/linecorp/armeria/common/HttpResponseBuilder.java Outdated
Comment thread core/src/test/java/com/linecorp/armeria/common/HttpResponseBuilderTest.java Outdated
@ikhoon ikhoon added the defect label Nov 30, 2021
@ikhoon
Copy link
Copy Markdown
Contributor

ikhoon commented Nov 30, 2021

Added defect label because this PR also fixes #3932 (comment)

Copy link
Copy Markdown
Contributor

@ikhoon ikhoon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Almost there! Left only minor nits.

Comment thread core/src/main/java/com/linecorp/armeria/common/AbstractHttpRequestBuilder.java Outdated
@ikhoon ikhoon added this to the 1.14.0 milestone Dec 2, 2021
Copy link
Copy Markdown
Contributor

@jrhee17 jrhee17 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes look really nice 👍 LGTM apart from minor comments. Thank you! 🙇

}

AbstractHttpMessageBuilder contentJson(Object content) {
requireNonNull(content, "content");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
    }
}

Comment thread core/src/main/java/com/linecorp/armeria/common/AbstractHttpRequestBuilder.java Outdated
Comment thread core/src/main/java/com/linecorp/armeria/common/AbstractHttpMessageBuilder.java Outdated
* Sets 200 OK to the status of this response.
*/
public HttpResponseBuilder ok() {
status(HttpStatus.OK);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit+optional; could just return as-is

Suggested change
status(HttpStatus.OK);
return status(HttpStatus.OK);

Comment thread core/src/main/java/com/linecorp/armeria/common/HttpResponseBuilder.java Outdated
package com.linecorp.armeria.common;

import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer to use import static org.assertj.core.api.Assertions.assertThat; so that we can take full advantage of the assertj API

Comment thread core/src/test/java/com/linecorp/armeria/common/HttpResponseBuilderTest.java Outdated
ta7uw and others added 5 commits December 6, 2021 13:16
Copy link
Copy Markdown
Contributor

@ikhoon ikhoon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good. Left only minor nits.

}

/**
* Sets a header for this response. For example:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adds

}

/**
* Sets multiple headers for this response. For example:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adds

Copy link
Copy Markdown
Contributor

@minwoox minwoox left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great job! Thanks, @ta7uw! 😄

Comment thread core/src/main/java/com/linecorp/armeria/common/AbstractHttpMessageBuilder.java Outdated
@ikhoon
Copy link
Copy Markdown
Contributor

ikhoon commented Dec 8, 2021

Revert? A publisher will be sent with chunked transfer encoding.

Had a chat internally, my previous comment does not comply with Armeria's policy.
If users can predict the total size of a Publisher, we need to respect it.
Please revive if (publisher() == null) { condition.
Sorry for the back and forth. 😅

AbstractHttpMessageBuilder contentJson(Object content) {
requireNonNull(content, "content");
checkState(publisher == null, "publisher has been set already");
headersBuilder().contentType(MediaType.JSON);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be removed now?

Suggested change
headersBuilder().contentType(MediaType.JSON);

StreamMessage.concat(publisher, StreamMessage.of(httpTrailers.build())));
}
}
final HttpData content = content();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK

@ta7uw ta7uw requested a review from ikhoon December 10, 2021 02:10
Copy link
Copy Markdown
Contributor

@ikhoon ikhoon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot, @ta7uw! 🎉🚀
Left a minor comment. 🙇‍♂️

Comment thread core/src/main/java/com/linecorp/armeria/common/HttpResponse.java
@minwoox minwoox merged commit 5c521cc into line:master Dec 13, 2021
@minwoox
Copy link
Copy Markdown
Contributor

minwoox commented Dec 13, 2021

Now I can fluently build my response. 🤣
Great work, @ta7uw! 🎉 🎉 🎉
And thanks, reviewers!

@ta7uw ta7uw deleted the issue-3398 branch May 27, 2022 15:05
jrhee17 pushed a commit that referenced this pull request Aug 21, 2023
…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]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Provide a way to fluently build an HttpResponse

4 participants