diff --git a/akka-http-core/src/main/scala/akka/http/scaladsl/model/StatusCode.scala b/akka-http-core/src/main/scala/akka/http/scaladsl/model/StatusCode.scala index be577648a9..6a0770fa55 100644 --- a/akka-http-core/src/main/scala/akka/http/scaladsl/model/StatusCode.scala +++ b/akka-http-core/src/main/scala/akka/http/scaladsl/model/StatusCode.scala @@ -107,7 +107,7 @@ object StatusCodes extends ObjectRegistry[Int, StatusCode] { val Created = reg(s(201)("Created", "The request has been fulfilled and resulted in a new resource being created.")) val Accepted = reg(s(202)("Accepted", "The request has been accepted for processing, but the processing has not been completed.")) val NonAuthoritativeInformation = reg(s(203)("Non-Authoritative Information", "The server successfully processed the request, but is returning information that may be from another source.")) - val NoContent = reg(s(204)("No Content", "", allowsEntity = false)) + val NoContent = reg(s(204)("No Content", "The server successfully processed the request and is not returning any content.", allowsEntity = false)) val ResetContent = reg(s(205)("Reset Content", "The server successfully processed the request, but is not returning any content.")) val PartialContent = reg(s(206)("Partial Content", "The server is delivering only part of the resource due to a range header sent by the client.")) val MultiStatus = reg(s(207)("Multi-Status", "The message body that follows is an XML message and can contain a number of separate response codes, depending on how many sub-requests were made.")) diff --git a/akka-http-testkit/src/main/scala/akka/http/scaladsl/testkit/MarshallingTestUtils.scala b/akka-http-testkit/src/main/scala/akka/http/scaladsl/testkit/MarshallingTestUtils.scala index 8558ad985e..2baaeaeccc 100644 --- a/akka-http-testkit/src/main/scala/akka/http/scaladsl/testkit/MarshallingTestUtils.scala +++ b/akka-http-testkit/src/main/scala/akka/http/scaladsl/testkit/MarshallingTestUtils.scala @@ -5,10 +5,10 @@ package akka.http.scaladsl.testkit import scala.concurrent.duration._ -import scala.concurrent.{ ExecutionContext, Await } -import akka.http.scaladsl.unmarshalling.{ Unmarshal, FromEntityUnmarshaller } +import scala.concurrent.{ Await, ExecutionContext } +import akka.http.scaladsl.unmarshalling.{ FromEntityUnmarshaller, Unmarshal } import akka.http.scaladsl.marshalling._ -import akka.http.scaladsl.model.HttpEntity +import akka.http.scaladsl.model.{ HttpEntity, HttpRequest, HttpResponse } import akka.stream.Materializer import scala.util.Try @@ -17,6 +17,10 @@ trait MarshallingTestUtils { def marshal[T: ToEntityMarshaller](value: T)(implicit ec: ExecutionContext, mat: Materializer): HttpEntity.Strict = Await.result(Marshal(value).to[HttpEntity].flatMap(_.toStrict(1.second)), 1.second) + def marshalToResponse[T: ToResponseMarshaller](value: T, request: HttpRequest = HttpRequest())(implicit ec: ExecutionContext, mat: Materializer): HttpResponse = { + Await.result(Marshal(value).toResponseFor(request), 1.second) + } + def unmarshalValue[T: FromEntityUnmarshaller](entity: HttpEntity)(implicit ec: ExecutionContext, mat: Materializer): T = unmarshal(entity).get diff --git a/akka-http-tests/src/test/scala/akka/http/scaladsl/marshalling/MarshallingSpec.scala b/akka-http-tests/src/test/scala/akka/http/scaladsl/marshalling/MarshallingSpec.scala index 4d73ee31a1..ab66c92a98 100644 --- a/akka-http-tests/src/test/scala/akka/http/scaladsl/marshalling/MarshallingSpec.scala +++ b/akka-http-tests/src/test/scala/akka/http/scaladsl/marshalling/MarshallingSpec.scala @@ -23,7 +23,7 @@ class MarshallingSpec extends FreeSpec with Matchers with BeforeAndAfterAll with implicit val materializer = ActorMaterializer() import system.dispatcher - "The PredefinedToEntityMarshallers." - { + "The PredefinedToEntityMarshallers" - { "StringMarshaller should marshal strings to `text/plain` content in UTF-8" in { marshal("Ha“llo") shouldEqual HttpEntity("Ha“llo") } @@ -39,7 +39,17 @@ class MarshallingSpec extends FreeSpec with Matchers with BeforeAndAfterAll with } } - "The GenericMarshallers." - { + "The PredefinedToResponseMarshallers" - { + "fromStatusCode should properly marshal entities that are not supposed to have a body" in { + marshalToResponse(StatusCodes.NoContent) shouldEqual HttpResponse(StatusCodes.NoContent, entity = HttpEntity.Empty) + } + "fromStatusCode should properly marshal entities that contain pre-defined content" in { + marshalToResponse(StatusCodes.EnhanceYourCalm) shouldEqual + HttpResponse(StatusCodes.EnhanceYourCalm, entity = HttpEntity(StatusCodes.EnhanceYourCalm.defaultMessage)) + } + } + + "The GenericMarshallers" - { "optionMarshaller should enable marshalling of Option[T]" in { marshal(Some("Ha“llo")) shouldEqual HttpEntity("Ha“llo") @@ -51,7 +61,7 @@ class MarshallingSpec extends FreeSpec with Matchers with BeforeAndAfterAll with } } - "The MultipartMarshallers." - { + "The MultipartMarshallers" - { "multipartMarshaller should correctly marshal multipart content with" - { "one empty part" in { marshal(Multipart.General(`multipart/mixed`, Multipart.General.BodyPart.Strict(""))) shouldEqual HttpEntity( diff --git a/akka-http-tests/src/test/scala/akka/http/scaladsl/server/directives/CodingDirectivesSpec.scala b/akka-http-tests/src/test/scala/akka/http/scaladsl/server/directives/CodingDirectivesSpec.scala index 9006290233..fa52405e97 100644 --- a/akka-http-tests/src/test/scala/akka/http/scaladsl/server/directives/CodingDirectivesSpec.scala +++ b/akka-http-tests/src/test/scala/akka/http/scaladsl/server/directives/CodingDirectivesSpec.scala @@ -478,6 +478,27 @@ class CodingDirectivesSpec extends RoutingSpec with Inside { } } + "the default marshaller" should { + "allow compressed responses with no body for informational messages" in { + Get() ~> `Accept-Encoding`(HttpEncodings.compress) ~> { + encodeResponse { + complete { StatusCodes.Continue } + } + } ~> check { + status shouldBe StatusCodes.Continue + } + } + "allow gzipped responses with no body for 204 messages" in { + Get() ~> `Accept-Encoding`(HttpEncodings.gzip) ~> { + encodeResponse { + complete { StatusCodes.NoContent } + } + } ~> check { + status shouldBe StatusCodes.NoContent + } + } + } + def compress(input: String, encoder: Encoder): ByteString = { val compressor = encoder.newCompressor compressor.compressAndFlush(ByteString(input)) ++ compressor.finish() diff --git a/akka-http/src/main/scala/akka/http/scaladsl/marshalling/PredefinedToResponseMarshallers.scala b/akka-http/src/main/scala/akka/http/scaladsl/marshalling/PredefinedToResponseMarshallers.scala index 35bc19f3d0..49ff565a37 100644 --- a/akka-http/src/main/scala/akka/http/scaladsl/marshalling/PredefinedToResponseMarshallers.scala +++ b/akka-http/src/main/scala/akka/http/scaladsl/marshalling/PredefinedToResponseMarshallers.scala @@ -5,17 +5,15 @@ package akka.http.scaladsl.marshalling import akka.http.scaladsl.common.EntityStreamingSupport -import akka.stream.impl.ConstantFun - -import scala.collection.immutable -import akka.http.scaladsl.util.FastFuture._ import akka.http.scaladsl.model.MediaTypes._ import akka.http.scaladsl.model._ -import akka.http.scaladsl.server.ContentNegotiator import akka.http.scaladsl.util.FastFuture +import akka.http.scaladsl.util.FastFuture._ +import akka.stream.impl.ConstantFun import akka.stream.scaladsl.Source import akka.util.ByteString +import scala.collection.immutable import scala.language.higherKinds trait PredefinedToResponseMarshallers extends LowPriorityToResponseMarshallerImplicits { @@ -33,7 +31,10 @@ trait PredefinedToResponseMarshallers extends LowPriorityToResponseMarshallerImp implicit val fromStatusCode: TRM[StatusCode] = Marshaller.withOpenCharset(`text/plain`) { (status, charset) ⇒ - HttpResponse(status, entity = HttpEntity(ContentType(`text/plain`, charset), status.defaultMessage)) + val responseEntity = + if (status.allowsEntity) HttpEntity(status.defaultMessage) + else HttpEntity.Empty + HttpResponse(status, entity = responseEntity) } implicit def fromStatusCodeAndValue[S, T](implicit sConv: S ⇒ StatusCode, mt: ToEntityMarshaller[T]): TRM[(S, T)] =