diff --git a/core/src/main/java/com/linecorp/armeria/client/BlockingWebClientRequestPreparation.java b/core/src/main/java/com/linecorp/armeria/client/BlockingWebClientRequestPreparation.java index d876ac8c6a6..492f6955be7 100644 --- a/core/src/main/java/com/linecorp/armeria/client/BlockingWebClientRequestPreparation.java +++ b/core/src/main/java/com/linecorp/armeria/client/BlockingWebClientRequestPreparation.java @@ -336,6 +336,12 @@ public BlockingWebClientRequestPreparation content(MediaType contentType, byte[] return this; } + @Override + public BlockingWebClientRequestPreparation content(HttpData content) { + delegate.content(content); + return this; + } + @Override public BlockingWebClientRequestPreparation content(MediaType contentType, HttpData content) { delegate.content(contentType, content); diff --git a/core/src/main/java/com/linecorp/armeria/client/FutureTransformingRequestPreparation.java b/core/src/main/java/com/linecorp/armeria/client/FutureTransformingRequestPreparation.java index 2fdce65350d..4ceadcfd1bf 100644 --- a/core/src/main/java/com/linecorp/armeria/client/FutureTransformingRequestPreparation.java +++ b/core/src/main/java/com/linecorp/armeria/client/FutureTransformingRequestPreparation.java @@ -269,6 +269,12 @@ public FutureTransformingRequestPreparation content(MediaType contentType, by return this; } + @Override + public FutureTransformingRequestPreparation content(HttpData content) { + delegate.content(content); + return this; + } + @Override public FutureTransformingRequestPreparation content(MediaType contentType, HttpData content) { delegate.content(contentType, content); diff --git a/core/src/main/java/com/linecorp/armeria/client/RestClientPreparation.java b/core/src/main/java/com/linecorp/armeria/client/RestClientPreparation.java index f913b070436..ba7b13d0513 100644 --- a/core/src/main/java/com/linecorp/armeria/client/RestClientPreparation.java +++ b/core/src/main/java/com/linecorp/armeria/client/RestClientPreparation.java @@ -192,6 +192,12 @@ public RestClientPreparation content(MediaType contentType, byte[] content) { return this; } + @Override + public RestClientPreparation content(HttpData content) { + delegate.content(content); + return this; + } + @Override public RestClientPreparation content(MediaType contentType, HttpData content) { delegate.content(contentType, content); diff --git a/core/src/main/java/com/linecorp/armeria/client/TransformingRequestPreparation.java b/core/src/main/java/com/linecorp/armeria/client/TransformingRequestPreparation.java index 535fc1c0e16..602b95d0e12 100644 --- a/core/src/main/java/com/linecorp/armeria/client/TransformingRequestPreparation.java +++ b/core/src/main/java/com/linecorp/armeria/client/TransformingRequestPreparation.java @@ -204,6 +204,12 @@ public TransformingRequestPreparation content(MediaType contentType, byte[ return this; } + @Override + public TransformingRequestPreparation content(HttpData content) { + delegate.content(content); + return this; + } + @Override public TransformingRequestPreparation content(MediaType contentType, HttpData content) { diff --git a/core/src/main/java/com/linecorp/armeria/client/WebClientRequestPreparation.java b/core/src/main/java/com/linecorp/armeria/client/WebClientRequestPreparation.java index a76d8008569..ab30a08fc6a 100644 --- a/core/src/main/java/com/linecorp/armeria/client/WebClientRequestPreparation.java +++ b/core/src/main/java/com/linecorp/armeria/client/WebClientRequestPreparation.java @@ -497,6 +497,11 @@ public WebClientRequestPreparation content(MediaType contentType, byte[] content return (WebClientRequestPreparation) super.content(contentType, content); } + @Override + public WebClientRequestPreparation content(HttpData content) { + return (WebClientRequestPreparation) super.content(content); + } + @Override public WebClientRequestPreparation content(MediaType contentType, HttpData content) { return (WebClientRequestPreparation) super.content(contentType, content); diff --git a/core/src/main/java/com/linecorp/armeria/common/AbstractHttpMessageBuilder.java b/core/src/main/java/com/linecorp/armeria/common/AbstractHttpMessageBuilder.java index 91e6e49e358..990ede3816e 100644 --- a/core/src/main/java/com/linecorp/armeria/common/AbstractHttpMessageBuilder.java +++ b/core/src/main/java/com/linecorp/armeria/common/AbstractHttpMessageBuilder.java @@ -90,15 +90,16 @@ public AbstractHttpMessageBuilder headers( @Override public AbstractHttpMessageBuilder content(String content) { - return content(MediaType.PLAIN_TEXT_UTF_8, content); + requireNonNull(content, "content"); + return content(HttpData.of(MediaType.PLAIN_TEXT_UTF_8.charset(StandardCharsets.UTF_8), + content)); } @Override public AbstractHttpMessageBuilder content(MediaType contentType, CharSequence content) { requireNonNull(contentType, "contentType"); requireNonNull(content, "content"); - return content(contentType, - HttpData.of(contentType.charset(StandardCharsets.UTF_8), + return content(contentType, HttpData.of(contentType.charset(StandardCharsets.UTF_8), content)); } @@ -107,13 +108,16 @@ public AbstractHttpMessageBuilder content(MediaType contentType, String content) requireNonNull(contentType, "contentType"); requireNonNull(content, "content"); return content(contentType, HttpData.of(contentType.charset(StandardCharsets.UTF_8), - content)); + content)); } @Override @FormatMethod public AbstractHttpMessageBuilder content(@FormatString String format, Object... content) { - return content(MediaType.PLAIN_TEXT_UTF_8, format, content); + requireNonNull(format, "format"); + requireNonNull(content, "content"); + return content(HttpData.of(MediaType.PLAIN_TEXT_UTF_8.charset(StandardCharsets.UTF_8), + format, content)); } @Override @@ -133,6 +137,14 @@ public AbstractHttpMessageBuilder content(MediaType contentType, byte[] content) return content(contentType, HttpData.wrap(content)); } + @Override + public AbstractHttpMessageBuilder content(HttpData content) { + requireNonNull(content, "content"); + checkState(publisher == null, "publisher has been set already"); + this.content = content; + return this; + } + @Override public AbstractHttpMessageBuilder content(MediaType contentType, HttpData content) { requireNonNull(contentType, "contentType"); diff --git a/core/src/main/java/com/linecorp/armeria/common/AbstractHttpRequestBuilder.java b/core/src/main/java/com/linecorp/armeria/common/AbstractHttpRequestBuilder.java index 5dee216529f..8b406ab0670 100644 --- a/core/src/main/java/com/linecorp/armeria/common/AbstractHttpRequestBuilder.java +++ b/core/src/main/java/com/linecorp/armeria/common/AbstractHttpRequestBuilder.java @@ -136,6 +136,11 @@ public AbstractHttpRequestBuilder content(MediaType contentType, byte[] content) return (AbstractHttpRequestBuilder) super.content(contentType, content); } + @Override + public AbstractHttpRequestBuilder content(HttpData content) { + return (AbstractHttpRequestBuilder) super.content(content); + } + @Override public AbstractHttpRequestBuilder content(MediaType contentType, HttpData content) { return (AbstractHttpRequestBuilder) super.content(contentType, content); diff --git a/core/src/main/java/com/linecorp/armeria/common/HttpMessageSetters.java b/core/src/main/java/com/linecorp/armeria/common/HttpMessageSetters.java index 5374f40c518..360637c19fd 100644 --- a/core/src/main/java/com/linecorp/armeria/common/HttpMessageSetters.java +++ b/core/src/main/java/com/linecorp/armeria/common/HttpMessageSetters.java @@ -69,6 +69,16 @@ HttpMessageSetters content(MediaType contentType, @FormatString String format, */ HttpMessageSetters content(MediaType contentType, byte[] content); + /** + * Sets the content for this message without setting a content-type header. + * The {@code content} will be wrapped using {@link HttpData#wrap(byte[])}, + * meaning any modifications made to {@code content} will be directly reflected in the request. + * + *

Note: This method does not set a content-type header. The caller is responsible + * for setting an appropriate content-type header if required. + */ + HttpMessageSetters content(HttpData content); + /** * Sets the content for this message. */ diff --git a/core/src/main/java/com/linecorp/armeria/common/HttpRequestBuilder.java b/core/src/main/java/com/linecorp/armeria/common/HttpRequestBuilder.java index 525fa3adcba..9b06fe8cffe 100644 --- a/core/src/main/java/com/linecorp/armeria/common/HttpRequestBuilder.java +++ b/core/src/main/java/com/linecorp/armeria/common/HttpRequestBuilder.java @@ -122,6 +122,11 @@ public HttpRequestBuilder content(MediaType contentType, byte[] content) { return (HttpRequestBuilder) super.content(contentType, content); } + @Override + public HttpRequestBuilder content(HttpData content) { + return (HttpRequestBuilder) super.content(content); + } + @Override public HttpRequestBuilder content(MediaType contentType, HttpData content) { return (HttpRequestBuilder) super.content(contentType, content); diff --git a/core/src/main/java/com/linecorp/armeria/common/HttpRequestSetters.java b/core/src/main/java/com/linecorp/armeria/common/HttpRequestSetters.java index fa438cab2ff..6ddb64490ed 100644 --- a/core/src/main/java/com/linecorp/armeria/common/HttpRequestSetters.java +++ b/core/src/main/java/com/linecorp/armeria/common/HttpRequestSetters.java @@ -76,6 +76,12 @@ HttpRequestSetters content(MediaType contentType, @FormatString String format, @Override HttpRequestSetters content(MediaType contentType, byte[] content); + /** + * Sets the content for this request. + */ + @Override + HttpRequestSetters content(HttpData content); + /** * Sets the content for this request. */ diff --git a/core/src/main/java/com/linecorp/armeria/common/HttpResponseBuilder.java b/core/src/main/java/com/linecorp/armeria/common/HttpResponseBuilder.java index 783d9be4c73..ca394bfdc8a 100644 --- a/core/src/main/java/com/linecorp/armeria/common/HttpResponseBuilder.java +++ b/core/src/main/java/com/linecorp/armeria/common/HttpResponseBuilder.java @@ -173,6 +173,11 @@ public HttpResponseBuilder content(MediaType contentType, byte[] content) { return (HttpResponseBuilder) super.content(contentType, content); } + @Override + public HttpResponseBuilder content(HttpData content) { + return (HttpResponseBuilder) super.content(content); + } + /** * Sets the content for this response. */ diff --git a/core/src/test/java/com/linecorp/armeria/client/HttpClientExpect100HeaderTest.java b/core/src/test/java/com/linecorp/armeria/client/HttpClientExpect100HeaderTest.java index c2c7115f2fd..454ea3fd26f 100644 --- a/core/src/test/java/com/linecorp/armeria/client/HttpClientExpect100HeaderTest.java +++ b/core/src/test/java/com/linecorp/armeria/client/HttpClientExpect100HeaderTest.java @@ -109,7 +109,7 @@ void continueToSendRequestOnHttp1() throws Exception { final CompletableFuture future = client.prepare() .post("/") - .content("foo\n") + .content(MediaType.PLAIN_TEXT_UTF_8, "foo\n") .header(HttpHeaderNames.EXPECT, HttpHeaderValues.CONTINUE) .execute() .aggregate(); @@ -167,7 +167,7 @@ void continueToSendRequestOnHttp2() throws Exception { final CompletableFuture future = client.prepare() .post("/") - .content("foo") + .content(MediaType.PLAIN_TEXT_UTF_8, "foo") .header(HttpHeaderNames.EXPECT, HttpHeaderValues.CONTINUE) .execute() .aggregate(); @@ -217,7 +217,7 @@ void expectationFailedHttp1() throws Exception { final CompletableFuture future = client.prepare() .post("/") - .content("foo\n") + .content(MediaType.PLAIN_TEXT_UTF_8, "foo\n") .header(HttpHeaderNames.EXPECT, HttpHeaderValues.CONTINUE) .execute() .aggregate(); @@ -268,7 +268,7 @@ void expectationFailedHttp2() throws Exception { final CompletableFuture future = client.prepare() .post("/") - .content("foo") + .content(MediaType.PLAIN_TEXT_UTF_8, "foo") .header(HttpHeaderNames.EXPECT, HttpHeaderValues.CONTINUE) .execute() .aggregate(); @@ -314,7 +314,7 @@ void receiveResponseWithStandardFlowOnHttp1() throws Exception { final CompletableFuture future = client.prepare() .post("/") - .content("foo\n") + .content(MediaType.PLAIN_TEXT_UTF_8, "foo\n") .header(HttpHeaderNames.EXPECT, HttpHeaderValues.CONTINUE) .execute() .aggregate(); @@ -369,7 +369,7 @@ void receiveResponseWithStandardFlowOnHttp2() throws Exception { final CompletableFuture future = client.prepare() .post("/") - .content("foo") + .content(MediaType.PLAIN_TEXT_UTF_8, "foo") .header(HttpHeaderNames.EXPECT, HttpHeaderValues.CONTINUE) .execute() .aggregate(); @@ -411,7 +411,7 @@ void timeoutFor100Continue() throws Exception { final CompletableFuture future = client.prepare() .post("/") - .content("foo\n") + .content(MediaType.PLAIN_TEXT_UTF_8, "foo\n") .header(HttpHeaderNames.EXPECT, HttpHeaderValues.CONTINUE) .execute() .aggregate(); diff --git a/core/src/test/java/com/linecorp/armeria/common/HttpResponseBuilderTest.java b/core/src/test/java/com/linecorp/armeria/common/HttpResponseBuilderTest.java index 1bcd166db74..0758c0a2e84 100644 --- a/core/src/test/java/com/linecorp/armeria/common/HttpResponseBuilderTest.java +++ b/core/src/test/java/com/linecorp/armeria/common/HttpResponseBuilderTest.java @@ -20,6 +20,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.nio.charset.StandardCharsets; +import java.util.List; import org.junit.jupiter.api.Test; @@ -102,7 +103,23 @@ void buildWithPlainContent() { final AggregatedHttpResponse aggregatedRes = res.aggregate().join(); assertThat(aggregatedRes.status()).isEqualTo(HttpStatus.OK); assertThat(aggregatedRes.contentUtf8()).isEqualTo("Armeriaはいろんな使い方がアルメリア"); - assertThat(aggregatedRes.contentType()).isEqualTo(MediaType.PLAIN_TEXT_UTF_8); + assertThat(aggregatedRes.contentType()).isNull(); + } + + @Test + void buildWithPlainAndContentTypeHeader() { + final HttpResponse res = HttpResponse.builder() + .ok() + .content("Armeriaはいろんな使い方がアルメリア") + .header(HttpHeaderNames.CONTENT_TYPE, MediaType.JSON) + .build(); + final AggregatedHttpResponse aggregatedRes = res.aggregate().join(); + assertThat(aggregatedRes.status()).isEqualTo(HttpStatus.OK); + assertThat(aggregatedRes.contentUtf8()).isEqualTo("Armeriaはいろんな使い方がアルメリア"); + + final List contentTypeHeaders = aggregatedRes.headers().getAll(HttpHeaderNames.CONTENT_TYPE); + assertThat(contentTypeHeaders).hasSize(1); + assertThat(contentTypeHeaders).containsExactly("application/json"); } @Test @@ -116,7 +133,24 @@ void buildWithPlainFormat() { final AggregatedHttpResponse aggregatedRes = res.aggregate().join(); assertThat(aggregatedRes.status()).isEqualTo(HttpStatus.OK); assertThat(aggregatedRes.contentUtf8()).isEqualTo("Armeriaはいろんな使い方がアルメリア"); - assertThat(aggregatedRes.contentType()).isEqualTo(MediaType.PLAIN_TEXT_UTF_8); + assertThat(aggregatedRes.contentType()).isNull(); + } + + @Test + void buildWithPlainFormatAndContentTypeHeader() { + final HttpResponse res = HttpResponse.builder() + .ok() + .content("%sはいろんな使い方が%s", + "Armeria", "アルメリア") + .header(HttpHeaderNames.CONTENT_TYPE, MediaType.JSON) + .build(); + final AggregatedHttpResponse aggregatedRes = res.aggregate().join(); + assertThat(aggregatedRes.status()).isEqualTo(HttpStatus.OK); + assertThat(aggregatedRes.contentUtf8()).isEqualTo("Armeriaはいろんな使い方がアルメリア"); + + final List contentTypeHeaders = aggregatedRes.headers().getAll(HttpHeaderNames.CONTENT_TYPE); + assertThat(contentTypeHeaders).hasSize(1); + assertThat(contentTypeHeaders).containsExactly("application/json"); } @Test @@ -256,7 +290,7 @@ void buildComplex() { assertThat(aggregatedRes.headers().getAll(HttpHeaderNames.ACCEPT_ENCODING)) .containsExactly("gzip", "deflate", "gzip"); assertThat(aggregatedRes.contentUtf8()).isEqualTo("Armeriaはいろんな使い方がアルメリア"); - assertThat(aggregatedRes.contentType()).isEqualTo(MediaType.PLAIN_TEXT_UTF_8); + assertThat(aggregatedRes.contentType()).isNull(); assertThat(aggregatedRes.trailers().contains("trailer-name")).isTrue(); assertThat(aggregatedRes.trailers().get("trailer-name")).isEqualTo("trailer-value"); } diff --git a/core/src/test/java/com/linecorp/armeria/server/Http1ServerDelayedCloseConnectionTest.java b/core/src/test/java/com/linecorp/armeria/server/Http1ServerDelayedCloseConnectionTest.java index c077dc97d47..799a78b60d0 100644 --- a/core/src/test/java/com/linecorp/armeria/server/Http1ServerDelayedCloseConnectionTest.java +++ b/core/src/test/java/com/linecorp/armeria/server/Http1ServerDelayedCloseConnectionTest.java @@ -50,6 +50,7 @@ protected void configure(ServerBuilder sb) { return HttpResponse.builder() .ok() .content("OK\n") + .header(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=utf-8") .header(HttpHeaderNames.CONNECTION, "close") .build(); }); diff --git a/scala/scala_2.13/src/main/scala/com/linecorp/armeria/client/scala/ScalaRestClientPreparation.scala b/scala/scala_2.13/src/main/scala/com/linecorp/armeria/client/scala/ScalaRestClientPreparation.scala index b0bf4b37b6f..df8e58058e1 100644 --- a/scala/scala_2.13/src/main/scala/com/linecorp/armeria/client/scala/ScalaRestClientPreparation.scala +++ b/scala/scala_2.13/src/main/scala/com/linecorp/armeria/client/scala/ScalaRestClientPreparation.scala @@ -25,13 +25,23 @@ import com.linecorp.armeria.client.{ RestClientPreparation } import com.linecorp.armeria.common.annotation.UnstableApi -import com.linecorp.armeria.common.{Cookie, ExchangeType, HttpData, HttpResponse, MediaType, ResponseEntity} +import com.linecorp.armeria.common.{ + Cookie, + ExchangeType, + HttpData, + HttpMessageSetters, + HttpResponse, + MediaType, + ResponseEntity +} import com.linecorp.armeria.scala.implicits._ import io.netty.util.AttributeKey + import java.lang.{Iterable => JIterable} import java.time.Duration import java.util.{Map => JMap} import org.reactivestreams.Publisher + import scala.collection.immutable import scala.concurrent.Future @@ -186,6 +196,11 @@ final class ScalaRestClientPreparation private[scala] (delegate: RestClientPrepa this } + override def content(content: HttpData): ScalaRestClientPreparation = { + delegate.content(content) + this + } + override def content(contentType: MediaType, content: HttpData): ScalaRestClientPreparation = { delegate.content(contentType, content) this