From c77f4e28f6057237549294be91fd6e8e9046973b Mon Sep 17 00:00:00 2001 From: Johannes Rudolph Date: Mon, 22 Sep 2014 11:56:10 +0200 Subject: [PATCH] !hco #15915 introduce more shades of HttpEntities The introduction of BodyParts again showed that not all entity types are useful for every kind of context. There are now these contexts where HttpEntities are used: - requests - responses - body parts And several kinds of entities: - Strict - Default - Chunked - CloseDelimited - IndefiniteLength To increase type safety of the API marker-interfaces are introduced defining which kinds of entities can be used in which contexts: - RequestEntity: Strict, Default, Chunked - ResponseEntity: Strict, Default, Chunked, CloseDelimited - BodyPartEntity: Strict, Default, IndefiniteLength Also, to be able still to provide abstractions over some kinds of entities additional auxiliary interfaces were necessary: - MessageEntity = RequestEntity >: ResponseEntity: Strict, Default, Chunked (type alias for RequestEntity) - UniversalEntity = RequestEntity with ResponseEntity with BodyPartEntity = Strict, Default --- .../akka/http/model/japi/BodyPartEntity.java | 8 ++ .../akka/http/model/japi/HttpEntities.java | 70 ++++++++++++++++ .../java/akka/http/model/japi/HttpEntity.java | 66 ++++----------- .../http/model/japi/HttpEntityChunked.java | 2 +- .../model/japi/HttpEntityCloseDelimited.java | 2 +- .../http/model/japi/HttpEntityDefault.java | 2 +- .../japi/HttpEntityIndefiniteLength.java | 15 ++++ .../http/model/japi/HttpEntityRegular.java | 10 --- .../http/model/japi/HttpEntityStrict.java | 2 +- .../akka/http/model/japi/HttpMessage.java | 12 +-- .../akka/http/model/japi/HttpRequest.java | 7 +- .../akka/http/model/japi/HttpResponse.java | 10 +++ .../akka/http/model/japi/RequestEntity.java | 8 ++ .../akka/http/model/japi/ResponseEntity.java | 8 ++ .../akka/http/model/japi/UniversalEntity.java | 8 ++ .../http/engine/parsing/BodyPartParser.scala | 4 +- .../engine/parsing/HttpMessageParser.scala | 8 +- .../engine/parsing/HttpRequestParser.scala | 2 +- .../engine/parsing/HttpResponseParser.scala | 2 +- .../http/engine/parsing/ParserOutput.scala | 4 +- .../engine/rendering/BodyPartRenderer.scala | 11 +-- .../HttpResponseRendererFactory.scala | 2 +- .../scala/akka/http/model/HttpEntity.scala | 80 ++++++++++++++----- .../scala/akka/http/model/HttpMessage.scala | 41 +++++----- .../akka/http/model/MultipartContent.scala | 14 ++-- .../akka/http/model/japi/JavaMapping.scala | 1 - .../main/scala/akka/http/model/package.scala | 10 +++ .../engine/parsing/RequestParserSpec.scala | 2 +- .../engine/parsing/ResponseParserSpec.scala | 2 +- .../testkit/RouteTestResultComponent.scala | 4 +- .../http/marshalling/MarshallingSpec.scala | 1 + .../akka/http/client/RequestBuilding.scala | 6 +- .../akka/http/marshalling/EmptyValue.scala | 4 +- .../akka/http/marshalling/Marshallers.scala | 2 +- .../http/marshalling/MediaTypeOverrider.scala | 4 +- .../PredefinedToEntityMarshallers.scala | 2 +- .../scala/akka/http/marshalling/package.scala | 6 +- .../akka/http/server/RequestContext.scala | 2 +- .../akka/http/server/RequestContextImpl.scala | 2 +- .../server/directives/BasicDirectives.scala | 2 +- 40 files changed, 289 insertions(+), 159 deletions(-) create mode 100644 akka-http-core/src/main/java/akka/http/model/japi/BodyPartEntity.java create mode 100644 akka-http-core/src/main/java/akka/http/model/japi/HttpEntities.java create mode 100644 akka-http-core/src/main/java/akka/http/model/japi/HttpEntityIndefiniteLength.java delete mode 100644 akka-http-core/src/main/java/akka/http/model/japi/HttpEntityRegular.java create mode 100644 akka-http-core/src/main/java/akka/http/model/japi/RequestEntity.java create mode 100644 akka-http-core/src/main/java/akka/http/model/japi/ResponseEntity.java create mode 100644 akka-http-core/src/main/java/akka/http/model/japi/UniversalEntity.java create mode 100644 akka-http-core/src/main/scala/akka/http/model/package.scala diff --git a/akka-http-core/src/main/java/akka/http/model/japi/BodyPartEntity.java b/akka-http-core/src/main/java/akka/http/model/japi/BodyPartEntity.java new file mode 100644 index 0000000000..791983fa43 --- /dev/null +++ b/akka-http-core/src/main/java/akka/http/model/japi/BodyPartEntity.java @@ -0,0 +1,8 @@ +/* + * Copyright (C) 2009-2014 Typesafe Inc. + */ + +package akka.http.model.japi; + +/** Marker-interface for entity types that can be used in a body part */ +public interface BodyPartEntity extends HttpEntity {} diff --git a/akka-http-core/src/main/java/akka/http/model/japi/HttpEntities.java b/akka-http-core/src/main/java/akka/http/model/japi/HttpEntities.java new file mode 100644 index 0000000000..c7bcfbcad9 --- /dev/null +++ b/akka-http-core/src/main/java/akka/http/model/japi/HttpEntities.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2009-2014 Typesafe Inc. + */ + +package akka.http.model.japi; + +import java.io.File; + +import akka.util.ByteString; +import org.reactivestreams.Publisher; + +import akka.stream.FlowMaterializer; +import akka.http.model.HttpEntity$; + +/** Constructors for HttpEntity instances */ +public final class HttpEntities { + private HttpEntities() {} + + public static HttpEntityStrict create(String string) { + return HttpEntity$.MODULE$.apply(string); + } + + public static HttpEntityStrict create(byte[] bytes) { + return HttpEntity$.MODULE$.apply(bytes); + } + + public static HttpEntityStrict create(ByteString bytes) { + return HttpEntity$.MODULE$.apply(bytes); + } + + public static HttpEntityStrict create(ContentType contentType, String string) { + return HttpEntity$.MODULE$.apply((akka.http.model.ContentType) contentType, string); + } + + public static HttpEntityStrict create(ContentType contentType, byte[] bytes) { + return HttpEntity$.MODULE$.apply((akka.http.model.ContentType) contentType, bytes); + } + + public static HttpEntityStrict create(ContentType contentType, ByteString bytes) { + return HttpEntity$.MODULE$.apply((akka.http.model.ContentType) contentType, bytes); + } + + public static UniversalEntity create(ContentType contentType, File file) { + return HttpEntity$.MODULE$.apply((akka.http.model.ContentType) contentType, file); + } + + public static HttpEntityDefault create(ContentType contentType, long contentLength, Publisher data) { + return new akka.http.model.HttpEntity.Default((akka.http.model.ContentType) contentType, contentLength, data); + } + + public static HttpEntityCloseDelimited createCloseDelimited(ContentType contentType, Publisher data) { + return new akka.http.model.HttpEntity.CloseDelimited((akka.http.model.ContentType) contentType, data); + } + + public static HttpEntityIndefiniteLength createIndefiniteLength(ContentType contentType, Publisher data) { + return new akka.http.model.HttpEntity.IndefiniteLength((akka.http.model.ContentType) contentType, data); + } + + public static HttpEntityChunked createChunked(ContentType contentType, Publisher chunks) { + return new akka.http.model.HttpEntity.Chunked( + (akka.http.model.ContentType) contentType, + Util.upcastPublisher(chunks)); + } + + public static HttpEntityChunked createChunked(ContentType contentType, Publisher data, FlowMaterializer materializer) { + return akka.http.model.HttpEntity.Chunked$.MODULE$.fromData( + (akka.http.model.ContentType) contentType, + data, materializer); + } +} diff --git a/akka-http-core/src/main/java/akka/http/model/japi/HttpEntity.java b/akka-http-core/src/main/java/akka/http/model/japi/HttpEntity.java index 94ce4ac91c..b8bed69fab 100644 --- a/akka-http-core/src/main/java/akka/http/model/japi/HttpEntity.java +++ b/akka-http-core/src/main/java/akka/http/model/japi/HttpEntity.java @@ -9,8 +9,6 @@ import akka.stream.FlowMaterializer; import akka.util.ByteString; import org.reactivestreams.Publisher; -import java.io.File; - /** * Represents the entity of an Http message. An entity consists of the content-type of the data * and the actual data itself. Some subtypes of HttpEntity also define the content-length of the @@ -19,16 +17,24 @@ import java.io.File; * An HttpEntity can be of several kinds: * * - HttpEntity.Empty: the statically known empty entity + * - HttpEntityStrict: an entity containing already evaluated ByteString data * - HttpEntityDefault: the default entity which has a known length and which contains * a stream of ByteStrings. * - HttpEntityChunked: represents an entity that is delivered using `Transfer-Encoding: chunked` - * - HttpEntityCloseDelimited: the entity which doesn't have a fixed length but which is delimited by + * - HttpEntityCloseDelimited: an entity which doesn't have a fixed length but which is delimited by * closing the connection. + * - HttpEntityIndefiniteLength: an entity which doesn't have a fixed length which can be used to construct BodyParts + * with indefinite length * - * All entity subtypes but HttpEntityCloseDelimited are subtypes of {@link HttpEntityRegular} which - * means they can be used in Http request that disallow close-delimited transfer of the entity. + * Marker-interfaces denote which subclasses can be used in which context: + * - RequestEntity: an entity type that can be used in an HttpRequest + * - ResponseEntity: an entity type that can be used in an HttpResponse + * - BodyPartEntity: an entity type that can be used in a BodyPart + * - UniversalEntity: an entity type that can be used in every context + * + * Use the static constructors in HttpEntities to construct instances. */ -public abstract class HttpEntity { +public interface HttpEntity { /** * Returns the content-type of this entity */ @@ -45,11 +51,6 @@ public abstract class HttpEntity { */ public abstract boolean isKnownEmpty(); - /** - * Returns if this entity is a subtype of HttpEntityRegular. - */ - public abstract boolean isRegular(); - /** * Returns if this entity is a subtype of HttpEntityChunked. */ @@ -65,46 +66,13 @@ public abstract class HttpEntity { */ public abstract boolean isCloseDelimited(); + /** + * Returns if this entity is a subtype of HttpEntityIndefiniteLength. + */ + public abstract boolean isIndefiniteLength(); + /** * Returns a stream of data bytes this entity consists of. */ public abstract Publisher getDataBytes(FlowMaterializer materializer); - - public static HttpEntityStrict create(String string) { - return HttpEntity$.MODULE$.apply(string); - } - public static HttpEntityStrict create(byte[] bytes) { - return HttpEntity$.MODULE$.apply(bytes); - } - public static HttpEntityStrict create(ByteString bytes) { - return HttpEntity$.MODULE$.apply(bytes); - } - public static HttpEntityStrict create(ContentType contentType, String string) { - return HttpEntity$.MODULE$.apply((akka.http.model.ContentType) contentType, string); - } - public static HttpEntityStrict create(ContentType contentType, byte[] bytes) { - return HttpEntity$.MODULE$.apply((akka.http.model.ContentType) contentType, bytes); - } - public static HttpEntityStrict create(ContentType contentType, ByteString bytes) { - return HttpEntity$.MODULE$.apply((akka.http.model.ContentType) contentType, bytes); - } - public static HttpEntityRegular create(ContentType contentType, File file) { - return (HttpEntityRegular) HttpEntity$.MODULE$.apply((akka.http.model.ContentType) contentType, file); - } - public static HttpEntityDefault create(ContentType contentType, long contentLength, Publisher data) { - return new akka.http.model.HttpEntity.Default((akka.http.model.ContentType) contentType, contentLength, data); - } - public static HttpEntityCloseDelimited createCloseDelimited(ContentType contentType, Publisher data) { - return new akka.http.model.HttpEntity.CloseDelimited((akka.http.model.ContentType) contentType, data); - } - public static HttpEntityChunked createChunked(ContentType contentType, Publisher chunks) { - return new akka.http.model.HttpEntity.Chunked( - (akka.http.model.ContentType) contentType, - Util.upcastPublisher(chunks)); - } - public static HttpEntityChunked createChunked(ContentType contentType, Publisher data, FlowMaterializer materializer) { - return akka.http.model.HttpEntity.Chunked$.MODULE$.fromData( - (akka.http.model.ContentType) contentType, - data, materializer); - } } diff --git a/akka-http-core/src/main/java/akka/http/model/japi/HttpEntityChunked.java b/akka-http-core/src/main/java/akka/http/model/japi/HttpEntityChunked.java index 2c162034b2..00e6d38816 100644 --- a/akka-http-core/src/main/java/akka/http/model/japi/HttpEntityChunked.java +++ b/akka-http-core/src/main/java/akka/http/model/japi/HttpEntityChunked.java @@ -10,6 +10,6 @@ import org.reactivestreams.Publisher; * Represents an entity transferred using `Transfer-Encoding: chunked`. It consists of a * stream of {@link ChunkStreamPart}. */ -public abstract class HttpEntityChunked extends HttpEntityRegular { +public abstract class HttpEntityChunked implements RequestEntity, ResponseEntity { public abstract Publisher getChunks(); } diff --git a/akka-http-core/src/main/java/akka/http/model/japi/HttpEntityCloseDelimited.java b/akka-http-core/src/main/java/akka/http/model/japi/HttpEntityCloseDelimited.java index 26f3b45a43..9abde7b677 100644 --- a/akka-http-core/src/main/java/akka/http/model/japi/HttpEntityCloseDelimited.java +++ b/akka-http-core/src/main/java/akka/http/model/japi/HttpEntityCloseDelimited.java @@ -12,6 +12,6 @@ import org.reactivestreams.Publisher; * determined by closing the underlying connection. Therefore, this entity type is only * available for Http responses. */ -public abstract class HttpEntityCloseDelimited extends HttpEntity { +public abstract class HttpEntityCloseDelimited implements ResponseEntity { public abstract Publisher data(); } diff --git a/akka-http-core/src/main/java/akka/http/model/japi/HttpEntityDefault.java b/akka-http-core/src/main/java/akka/http/model/japi/HttpEntityDefault.java index 7b4a5e8fa8..e09ebd1a68 100644 --- a/akka-http-core/src/main/java/akka/http/model/japi/HttpEntityDefault.java +++ b/akka-http-core/src/main/java/akka/http/model/japi/HttpEntityDefault.java @@ -10,7 +10,7 @@ import org.reactivestreams.Publisher; /** * The default entity type which has a predetermined length and a stream of data bytes. */ -public abstract class HttpEntityDefault extends HttpEntityRegular { +public abstract class HttpEntityDefault implements BodyPartEntity, RequestEntity, ResponseEntity { public abstract long contentLength(); public abstract Publisher data(); } diff --git a/akka-http-core/src/main/java/akka/http/model/japi/HttpEntityIndefiniteLength.java b/akka-http-core/src/main/java/akka/http/model/japi/HttpEntityIndefiniteLength.java new file mode 100644 index 0000000000..d831ae9297 --- /dev/null +++ b/akka-http-core/src/main/java/akka/http/model/japi/HttpEntityIndefiniteLength.java @@ -0,0 +1,15 @@ +/* + * Copyright (C) 2009-2014 Typesafe Inc. + */ + +package akka.http.model.japi; + +import akka.util.ByteString; +import org.reactivestreams.Publisher; + +/** + * Represents an entity without a predetermined content-length to use in a BodyParts. + */ +public abstract class HttpEntityIndefiniteLength implements BodyPartEntity { + public abstract Publisher data(); +} \ No newline at end of file diff --git a/akka-http-core/src/main/java/akka/http/model/japi/HttpEntityRegular.java b/akka-http-core/src/main/java/akka/http/model/japi/HttpEntityRegular.java deleted file mode 100644 index db751056e7..0000000000 --- a/akka-http-core/src/main/java/akka/http/model/japi/HttpEntityRegular.java +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Copyright (C) 2009-2014 Typesafe Inc. - */ - -package akka.http.model.japi; - -/** - * A marker type that denotes HttpEntity subtypes that can be used in Http requests. - */ -public abstract class HttpEntityRegular extends HttpEntity {} diff --git a/akka-http-core/src/main/java/akka/http/model/japi/HttpEntityStrict.java b/akka-http-core/src/main/java/akka/http/model/japi/HttpEntityStrict.java index dd314b8a7e..711a7a7666 100644 --- a/akka-http-core/src/main/java/akka/http/model/japi/HttpEntityStrict.java +++ b/akka-http-core/src/main/java/akka/http/model/japi/HttpEntityStrict.java @@ -9,6 +9,6 @@ import akka.util.ByteString; /** * The entity type which consists of a predefined fixed ByteString of data. */ -public abstract class HttpEntityStrict extends HttpEntityRegular { +public abstract class HttpEntityStrict implements BodyPartEntity, RequestEntity, ResponseEntity { public abstract ByteString data(); } diff --git a/akka-http-core/src/main/java/akka/http/model/japi/HttpMessage.java b/akka-http-core/src/main/java/akka/http/model/japi/HttpMessage.java index 122c4fcce9..1c129f6dea 100644 --- a/akka-http-core/src/main/java/akka/http/model/japi/HttpMessage.java +++ b/akka-http-core/src/main/java/akka/http/model/japi/HttpMessage.java @@ -48,7 +48,7 @@ public interface HttpMessage { /** * The entity of this message. */ - HttpEntity entity(); + ResponseEntity entity(); public static interface MessageTransformations { /** @@ -71,11 +71,6 @@ public interface HttpMessage { */ Self removeHeader(String headerName); - /** - * Returns a copy of this message with a new entity. - */ - Self withEntity(HttpEntity entity); - /** * Returns a copy of this message with a new entity. */ @@ -110,5 +105,10 @@ public interface HttpMessage { * Returns a copy of Self message with a new entity. */ Self withEntity(ContentType type, File file); + + /** + * Returns a copy of Self message with a new entity. + */ + Self withEntity(RequestEntity entity); } } diff --git a/akka-http-core/src/main/java/akka/http/model/japi/HttpRequest.java b/akka-http-core/src/main/java/akka/http/model/japi/HttpRequest.java index 1c9f6e6b8c..bcd12b7211 100644 --- a/akka-http-core/src/main/java/akka/http/model/japi/HttpRequest.java +++ b/akka-http-core/src/main/java/akka/http/model/japi/HttpRequest.java @@ -21,7 +21,7 @@ public abstract class HttpRequest implements HttpMessage, HttpMessage.MessageTra /** * Returns the entity of this request. */ - public abstract HttpEntityRegular entity(); + public abstract RequestEntity entity(); /** * Returns a copy of this instance with a new method. @@ -38,6 +38,11 @@ public abstract class HttpRequest implements HttpMessage, HttpMessage.MessageTra */ public abstract HttpRequest withUri(String path); + /** + * Returns a copy of this instance with a new entity. + */ + public abstract HttpRequest withEntity(RequestEntity entity); + /** * Returns a default request to be changed using the `withX` methods. */ diff --git a/akka-http-core/src/main/java/akka/http/model/japi/HttpResponse.java b/akka-http-core/src/main/java/akka/http/model/japi/HttpResponse.java index 5ade0343c8..b1026cc4eb 100644 --- a/akka-http-core/src/main/java/akka/http/model/japi/HttpResponse.java +++ b/akka-http-core/src/main/java/akka/http/model/japi/HttpResponse.java @@ -13,6 +13,11 @@ public abstract class HttpResponse implements HttpMessage, HttpMessage.MessageTr */ public abstract StatusCode status(); + /** + * Returns the entity of this request. + */ + public abstract ResponseEntity entity(); + /** * Returns a copy of this instance with a new status-code. */ @@ -23,6 +28,11 @@ public abstract class HttpResponse implements HttpMessage, HttpMessage.MessageTr */ public abstract HttpResponse withStatus(int statusCode); + /** + * Returns a copy of this instance with a new entity. + */ + public abstract HttpResponse withEntity(ResponseEntity entity); + /** * Returns a default response to be changed using the `withX` methods. */ diff --git a/akka-http-core/src/main/java/akka/http/model/japi/RequestEntity.java b/akka-http-core/src/main/java/akka/http/model/japi/RequestEntity.java new file mode 100644 index 0000000000..3025f8c23b --- /dev/null +++ b/akka-http-core/src/main/java/akka/http/model/japi/RequestEntity.java @@ -0,0 +1,8 @@ +/* + * Copyright (C) 2009-2014 Typesafe Inc. + */ + +package akka.http.model.japi; + +/** Marker-interface for entity types that can be used in a request */ +public interface RequestEntity extends ResponseEntity {} diff --git a/akka-http-core/src/main/java/akka/http/model/japi/ResponseEntity.java b/akka-http-core/src/main/java/akka/http/model/japi/ResponseEntity.java new file mode 100644 index 0000000000..dc9619ed17 --- /dev/null +++ b/akka-http-core/src/main/java/akka/http/model/japi/ResponseEntity.java @@ -0,0 +1,8 @@ +/* + * Copyright (C) 2009-2014 Typesafe Inc. + */ + +package akka.http.model.japi; + +/** Marker-interface for entity types that can be used in a response */ +public interface ResponseEntity extends HttpEntity {} diff --git a/akka-http-core/src/main/java/akka/http/model/japi/UniversalEntity.java b/akka-http-core/src/main/java/akka/http/model/japi/UniversalEntity.java new file mode 100644 index 0000000000..7bcf5369e7 --- /dev/null +++ b/akka-http-core/src/main/java/akka/http/model/japi/UniversalEntity.java @@ -0,0 +1,8 @@ +/* + * Copyright (C) 2009-2014 Typesafe Inc. + */ + +package akka.http.model.japi; + +/** Marker-interface for entity types that can be used in any context */ +public interface UniversalEntity extends RequestEntity, ResponseEntity, BodyPartEntity {} diff --git a/akka-http-core/src/main/scala/akka/http/engine/parsing/BodyPartParser.scala b/akka-http-core/src/main/scala/akka/http/engine/parsing/BodyPartParser.scala index 198693026d..03c49e523f 100644 --- a/akka-http-core/src/main/scala/akka/http/engine/parsing/BodyPartParser.scala +++ b/akka-http-core/src/main/scala/akka/http/engine/parsing/BodyPartParser.scala @@ -143,7 +143,7 @@ private[http] final class BodyPartParser(defaultContentType: ContentType, def parseEntity(headers: List[HttpHeader], contentType: ContentType, emitPartChunk: (List[HttpHeader], ContentType, ByteString) ⇒ Unit = { (headers, ct, bytes) ⇒ - emit(BodyPartStart(headers, entityParts ⇒ HttpEntity.CloseDelimited(ct, + emit(BodyPartStart(headers, entityParts ⇒ HttpEntity.IndefiniteLength(ct, Flow(entityParts).collect { case EntityPart(data) ⇒ data }.toPublisher()))) emit(bytes) }, @@ -217,7 +217,7 @@ private[http] object BodyPartParser { val boundaryCharNoSpace = CharPredicate.Digit ++ CharPredicate.Alpha ++ "'()+_,-./:=?" sealed trait Output - final case class BodyPartStart(headers: List[HttpHeader], createEntity: Publisher[Output] ⇒ HttpEntity) extends Output + final case class BodyPartStart(headers: List[HttpHeader], createEntity: Publisher[Output] ⇒ BodyPartEntity) extends Output final case class EntityPart(data: ByteString) extends Output final case class ParseError(info: ErrorInfo) extends Output diff --git a/akka-http-core/src/main/scala/akka/http/engine/parsing/HttpMessageParser.scala b/akka-http-core/src/main/scala/akka/http/engine/parsing/HttpMessageParser.scala index 58d17b03e5..43856fae3d 100644 --- a/akka-http-core/src/main/scala/akka/http/engine/parsing/HttpMessageParser.scala +++ b/akka-http-core/src/main/scala/akka/http/engine/parsing/HttpMessageParser.scala @@ -231,19 +231,19 @@ private[http] abstract class HttpMessageParser[Output >: ParserOutput.MessageOut case None ⇒ ContentTypes.`application/octet-stream` } - def emptyEntity(cth: Option[`Content-Type`])(entityParts: Any): HttpEntity.Regular = + def emptyEntity(cth: Option[`Content-Type`])(entityParts: Any): UniversalEntity = if (cth.isDefined) HttpEntity.empty(cth.get.contentType) else HttpEntity.Empty def strictEntity(cth: Option[`Content-Type`], input: ByteString, bodyStart: Int, - contentLength: Int)(entityParts: Any): HttpEntity.Regular = + contentLength: Int)(entityParts: Any): UniversalEntity = HttpEntity.Strict(contentType(cth), input.slice(bodyStart, bodyStart + contentLength)) - def defaultEntity(cth: Option[`Content-Type`], contentLength: Long)(entityParts: Publisher[_ <: ParserOutput])(implicit fm: FlowMaterializer): HttpEntity.Regular = { + def defaultEntity(cth: Option[`Content-Type`], contentLength: Long)(entityParts: Publisher[_ <: ParserOutput])(implicit fm: FlowMaterializer): UniversalEntity = { val data = Flow(entityParts).collect { case ParserOutput.EntityPart(bytes) ⇒ bytes }.toPublisher() HttpEntity.Default(contentType(cth), contentLength, data) } - def chunkedEntity(cth: Option[`Content-Type`])(entityChunks: Publisher[_ <: ParserOutput])(implicit fm: FlowMaterializer): HttpEntity.Regular = { + def chunkedEntity(cth: Option[`Content-Type`])(entityChunks: Publisher[_ <: ParserOutput])(implicit fm: FlowMaterializer): RequestEntity with ResponseEntity = { val chunks = Flow(entityChunks).collect { case ParserOutput.EntityChunk(chunk) ⇒ chunk }.toPublisher() HttpEntity.Chunked(contentType(cth), chunks) } diff --git a/akka-http-core/src/main/scala/akka/http/engine/parsing/HttpRequestParser.scala b/akka-http-core/src/main/scala/akka/http/engine/parsing/HttpRequestParser.scala index 8d754f601b..d41ce67af1 100644 --- a/akka-http-core/src/main/scala/akka/http/engine/parsing/HttpRequestParser.scala +++ b/akka-http-core/src/main/scala/akka/http/engine/parsing/HttpRequestParser.scala @@ -108,7 +108,7 @@ private[http] class HttpRequestParser(_settings: ParserSettings, clh: Option[`Content-Length`], cth: Option[`Content-Type`], teh: Option[`Transfer-Encoding`], hostHeaderPresent: Boolean, closeAfterResponseCompletion: Boolean): StateResult = if (hostHeaderPresent || protocol == HttpProtocols.`HTTP/1.0`) { - def emitRequestStart(createEntity: Publisher[ParserOutput.RequestOutput] ⇒ HttpEntity.Regular) = + def emitRequestStart(createEntity: Publisher[ParserOutput.RequestOutput] ⇒ RequestEntity) = emit(ParserOutput.RequestStart(method, uri, protocol, headers, createEntity, closeAfterResponseCompletion)) teh match { diff --git a/akka-http-core/src/main/scala/akka/http/engine/parsing/HttpResponseParser.scala b/akka-http-core/src/main/scala/akka/http/engine/parsing/HttpResponseParser.scala index 95b5e1073c..571982e090 100644 --- a/akka-http-core/src/main/scala/akka/http/engine/parsing/HttpResponseParser.scala +++ b/akka-http-core/src/main/scala/akka/http/engine/parsing/HttpResponseParser.scala @@ -75,7 +75,7 @@ private[http] class HttpResponseParser(_settings: ParserSettings, def parseEntity(headers: List[HttpHeader], protocol: HttpProtocol, input: ByteString, bodyStart: Int, clh: Option[`Content-Length`], cth: Option[`Content-Type`], teh: Option[`Transfer-Encoding`], hostHeaderPresent: Boolean, closeAfterResponseCompletion: Boolean): StateResult = { - def emitResponseStart(createEntity: Publisher[ParserOutput.ResponseOutput] ⇒ HttpEntity) = + def emitResponseStart(createEntity: Publisher[ParserOutput.ResponseOutput] ⇒ ResponseEntity) = emit(ParserOutput.ResponseStart(statusCode, protocol, headers, createEntity, closeAfterResponseCompletion)) def finishEmptyResponse() = { emitResponseStart(emptyEntity(cth)) diff --git a/akka-http-core/src/main/scala/akka/http/engine/parsing/ParserOutput.scala b/akka-http-core/src/main/scala/akka/http/engine/parsing/ParserOutput.scala index 5c956ae81c..521bec34d8 100644 --- a/akka-http-core/src/main/scala/akka/http/engine/parsing/ParserOutput.scala +++ b/akka-http-core/src/main/scala/akka/http/engine/parsing/ParserOutput.scala @@ -27,14 +27,14 @@ private[http] object ParserOutput { uri: Uri, protocol: HttpProtocol, headers: List[HttpHeader], - createEntity: Publisher[RequestOutput] ⇒ HttpEntity.Regular, + createEntity: Publisher[RequestOutput] ⇒ RequestEntity, closeAfterResponseCompletion: Boolean) extends MessageStart with RequestOutput final case class ResponseStart( statusCode: StatusCode, protocol: HttpProtocol, headers: List[HttpHeader], - createEntity: Publisher[ResponseOutput] ⇒ HttpEntity, + createEntity: Publisher[ResponseOutput] ⇒ ResponseEntity, closeAfterResponseCompletion: Boolean) extends MessageStart with ResponseOutput case object MessageEnd extends MessageOutput diff --git a/akka-http-core/src/main/scala/akka/http/engine/rendering/BodyPartRenderer.scala b/akka-http-core/src/main/scala/akka/http/engine/rendering/BodyPartRenderer.scala index 164414c3b1..fb29d90f09 100644 --- a/akka-http-core/src/main/scala/akka/http/engine/rendering/BodyPartRenderer.scala +++ b/akka-http-core/src/main/scala/akka/http/engine/rendering/BodyPartRenderer.scala @@ -67,13 +67,10 @@ private[http] class BodyPartRenderer(boundary: String, def completePartRendering(): List[Publisher[ChunkStreamPart]] = bodyPart.entity match { - case x if x.isKnownEmpty ⇒ chunkStream(r.get) - case Strict(_, data) ⇒ chunkStream((r ~~ data).get) - case Default(_, _, data) ⇒ bodyPartChunks(data) - case CloseDelimited(_, data) ⇒ bodyPartChunks(data) - case Chunked(_, chunks) ⇒ - val entityChunks = Flow(chunks).filter(!_.isLastChunk).toPublisher() - Flow(Chunk(r.get) :: Nil).concat(entityChunks).toPublisher() :: Nil + case x if x.isKnownEmpty ⇒ chunkStream(r.get) + case Strict(_, data) ⇒ chunkStream((r ~~ data).get) + case Default(_, _, data) ⇒ bodyPartChunks(data) + case IndefiniteLength(_, data) ⇒ bodyPartChunks(data) } renderBoundary() diff --git a/akka-http-core/src/main/scala/akka/http/engine/rendering/HttpResponseRendererFactory.scala b/akka-http-core/src/main/scala/akka/http/engine/rendering/HttpResponseRendererFactory.scala index 311cc1c614..2dcb597f51 100644 --- a/akka-http-core/src/main/scala/akka/http/engine/rendering/HttpResponseRendererFactory.scala +++ b/akka-http-core/src/main/scala/akka/http/engine/rendering/HttpResponseRendererFactory.scala @@ -118,7 +118,7 @@ private[http] class HttpResponseRendererFactory(serverHeader: Option[headers.Ser def byteStrings(entityBytes: ⇒ Publisher[ByteString]): List[Publisher[ByteString]] = renderByteStrings(r, entityBytes, skipEntity = noEntity) - def completeResponseRendering(entity: HttpEntity): List[Publisher[ByteString]] = + def completeResponseRendering(entity: ResponseEntity): List[Publisher[ByteString]] = entity match { case HttpEntity.Strict(_, data) ⇒ renderHeaders(headers.toList) diff --git a/akka-http-core/src/main/scala/akka/http/model/HttpEntity.scala b/akka-http-core/src/main/scala/akka/http/model/HttpEntity.scala index 33444aa692..a71b8f2f86 100644 --- a/akka-http-core/src/main/scala/akka/http/model/HttpEntity.scala +++ b/akka-http-core/src/main/scala/akka/http/model/HttpEntity.scala @@ -72,9 +72,26 @@ sealed trait HttpEntity extends japi.HttpEntity { // default implementations, should be overridden def isCloseDelimited: Boolean = false + def isIndefiniteLength: Boolean = false def isDefault: Boolean = false def isChunked: Boolean = false - def isRegular: Boolean = false +} + +/* An entity that can be used for body parts */ +sealed trait BodyPartEntity extends HttpEntity with japi.BodyPartEntity { + def withContentType(contentType: ContentType): BodyPartEntity +} +/* An entity that can be used for requests */ +sealed trait RequestEntity extends HttpEntity with japi.RequestEntity with ResponseEntity { + def withContentType(contentType: ContentType): RequestEntity +} +/* An entity that can be used for responses */ +sealed trait ResponseEntity extends HttpEntity with japi.ResponseEntity { + def withContentType(contentType: ContentType): ResponseEntity +} +/* An entity that can be used for requests, responses, and body parts */ +sealed trait UniversalEntity extends japi.UniversalEntity with MessageEntity with BodyPartEntity { + def withContentType(contentType: ContentType): UniversalEntity } object HttpEntity { @@ -87,10 +104,10 @@ object HttpEntity { if (bytes.length == 0) empty(contentType) else apply(contentType, ByteString(bytes)) def apply(contentType: ContentType, data: ByteString): Strict = if (data.isEmpty) empty(contentType) else Strict(contentType, data) - def apply(contentType: ContentType, contentLength: Long, data: Publisher[ByteString]): Regular = + def apply(contentType: ContentType, contentLength: Long, data: Publisher[ByteString]): UniversalEntity = if (contentLength == 0) empty(contentType) else Default(contentType, contentLength, data) - def apply(contentType: ContentType, file: File): Regular = { + def apply(contentType: ContentType, file: File): UniversalEntity = { val fileLength = file.length if (fileLength > 0) Default(contentType, fileLength, ???) // FIXME: attach from-file-Publisher else empty(contentType) @@ -102,23 +119,15 @@ object HttpEntity { if (contentType == Empty.contentType) Empty else Strict(contentType, data = ByteString.empty) - /** - * An HttpEntity that is "well-behaved" according to the HTTP/1.1 spec as that - * it is either chunked or defines a content-length that is known a-priori. - * Close-delimited entities are not `Regular` as they exists primarily for backwards compatibility with HTTP/1.0. - */ - sealed trait Regular extends japi.HttpEntityRegular with HttpEntity { - def withContentType(contentType: ContentType): HttpEntity.Regular - override def isRegular: Boolean = true - } - // TODO: re-establish serializability // TODO: equal/hashcode ? /** * The model for the entity of a "regular" unchunked HTTP message with known, fixed data. */ - final case class Strict(contentType: ContentType, data: ByteString) extends japi.HttpEntityStrict with Regular { + final case class Strict(contentType: ContentType, data: ByteString) + extends japi.HttpEntityStrict with UniversalEntity { + def isKnownEmpty: Boolean = data.isEmpty def dataBytes(implicit fm: FlowMaterializer): Publisher[ByteString] = SynchronousPublisherFromIterable(data :: Nil) @@ -135,7 +144,9 @@ object HttpEntity { */ final case class Default(contentType: ContentType, contentLength: Long, - data: Publisher[ByteString]) extends japi.HttpEntityDefault with Regular { + data: Publisher[ByteString]) + extends japi.HttpEntityDefault with UniversalEntity { + require(contentLength > 0, "contentLength must be positive (use `HttpEntity.empty(contentType)` for empty entities)") def isKnownEmpty = false override def isDefault: Boolean = true @@ -147,24 +158,51 @@ object HttpEntity { } /** - * The model for the entity of an HTTP response that is terminated by the server closing the connection. - * The content-length of such responses is unknown at the time the response headers have been received. - * Note that this type of HttpEntity cannot be used for HttpRequests! + * Supertype of CloseDelimited and IndefiniteLength. + * + * INTERNAL API */ - final case class CloseDelimited(contentType: ContentType, data: Publisher[ByteString]) extends japi.HttpEntityCloseDelimited with HttpEntity { + private[http] sealed trait WithoutKnownLength extends HttpEntity { + def contentType: ContentType + def data: Publisher[ByteString] + def isKnownEmpty = data eq EmptyPublisher - override def isCloseDelimited: Boolean = true def dataBytes(implicit fm: FlowMaterializer): Publisher[ByteString] = data + } + /** + * The model for the entity of an HTTP response that is terminated by the server closing the connection. + * The content-length of such responses is unknown at the time the response headers have been received. + * Note that this type of HttpEntity can only be used for HttpResponses. + */ + final case class CloseDelimited(contentType: ContentType, data: Publisher[ByteString]) + extends japi.HttpEntityCloseDelimited with ResponseEntity with WithoutKnownLength { + type Self = CloseDelimited + + override def isCloseDelimited: Boolean = true def withContentType(contentType: ContentType): CloseDelimited = if (contentType == this.contentType) this else copy(contentType = contentType) } + /** + * The model for the entity of a BodyPart with an indefinite length. + * Note that this type of HttpEntity can only be used for BodyParts. + */ + final case class IndefiniteLength(contentType: ContentType, data: Publisher[ByteString]) + extends japi.HttpEntityIndefiniteLength with BodyPartEntity with WithoutKnownLength { + + override def isIndefiniteLength: Boolean = true + def withContentType(contentType: ContentType): IndefiniteLength = + if (contentType == this.contentType) this else copy(contentType = contentType) + } + /** * The model for the entity of a chunked HTTP message (with `Transfer-Encoding: chunked`). */ - final case class Chunked(contentType: ContentType, chunks: Publisher[ChunkStreamPart]) extends japi.HttpEntityChunked with Regular { + final case class Chunked(contentType: ContentType, chunks: Publisher[ChunkStreamPart]) + extends japi.HttpEntityChunked with MessageEntity { + def isKnownEmpty = chunks eq EmptyPublisher override def isChunked: Boolean = true diff --git a/akka-http-core/src/main/scala/akka/http/model/HttpMessage.scala b/akka-http-core/src/main/scala/akka/http/model/HttpMessage.scala index f6693a9b12..e61c1558d0 100644 --- a/akka-http-core/src/main/scala/akka/http/model/HttpMessage.scala +++ b/akka-http-core/src/main/scala/akka/http/model/HttpMessage.scala @@ -25,7 +25,7 @@ sealed trait HttpMessage extends japi.HttpMessage { def isResponse: Boolean def headers: immutable.Seq[HttpHeader] - def entity: HttpEntity + def entity: ResponseEntity def protocol: HttpProtocol /** Returns a copy of this message with the list of headers set to the given ones. */ @@ -44,20 +44,20 @@ sealed trait HttpMessage extends japi.HttpMessage { } /** Returns a copy of this message with the entity set to the given one. */ - def withEntity(entity: japi.HttpEntity): Self + def withEntity(entity: MessageEntity): Self /** Returns a sharable and serializable copy of this message with a strict entity. */ def toStrict(timeout: FiniteDuration)(implicit ec: ExecutionContext, fm: FlowMaterializer): Deferrable[Self] = entity.toStrict(timeout).map(this.withEntity) /** Returns a copy of this message with the entity and headers set to the given ones. */ - def withHeadersAndEntity(headers: immutable.Seq[HttpHeader], entity: HttpEntity): Self + def withHeadersAndEntity(headers: immutable.Seq[HttpHeader], entity: MessageEntity): Self /** Returns a copy of this message with the list of headers transformed by the given function */ def mapHeaders(f: immutable.Seq[HttpHeader] ⇒ immutable.Seq[HttpHeader]): Self = withHeaders(f(headers)) /** Returns a copy of this message with the entity transformed by the given function */ - def mapEntity(f: HttpEntity ⇒ HttpEntity): Self = withEntity(f(entity)) + def mapEntity(f: HttpEntity ⇒ MessageEntity): Self = withEntity(f(entity)) /** * The content encoding as specified by the Content-Encoding header. If no Content-Encoding header is present the @@ -125,7 +125,7 @@ object HttpMessage { final case class HttpRequest(method: HttpMethod = HttpMethods.GET, uri: Uri = Uri./, headers: immutable.Seq[HttpHeader] = Nil, - entity: HttpEntity.Regular = HttpEntity.Empty, + entity: RequestEntity = HttpEntity.Empty, protocol: HttpProtocol = HttpProtocols.`HTTP/1.1`) extends japi.HttpRequest with HttpMessage { require(!uri.isEmpty, "An HttpRequest must not have an empty Uri") require(entity.isKnownEmpty || method.isEntityAccepted, "Requests with this method must have an empty entity") @@ -239,19 +239,11 @@ final case class HttpRequest(method: HttpMethod = HttpMethods.GET, override def withHeaders(headers: immutable.Seq[HttpHeader]): HttpRequest = if (headers eq this.headers) this else copy(headers = headers) - override def withEntity(entity: japi.HttpEntity): HttpRequest = - if (entity ne this.entity) entity match { - case x: HttpEntity.Regular ⇒ copy(entity = x) - case _ ⇒ throw new IllegalArgumentException("entity must be HttpEntity.Regular") - } - else this + override def withHeadersAndEntity(headers: immutable.Seq[HttpHeader], entity: RequestEntity): HttpRequest = copy(headers = headers, entity = entity) + override def withEntity(entity: japi.RequestEntity): HttpRequest = copy(entity = entity.asInstanceOf[RequestEntity]) + override def withEntity(entity: MessageEntity): HttpRequest = copy(entity = entity) - override def withHeadersAndEntity(headers: immutable.Seq[HttpHeader], entity: HttpEntity): HttpRequest = - if ((headers ne this.headers) || (entity ne this.entity)) entity match { - case x: HttpEntity.Regular ⇒ copy(headers = headers, entity = x) - case _ ⇒ throw new IllegalArgumentException("entity must be HttpEntity.Regular") - } - else this + def mapEntity(f: RequestEntity ⇒ RequestEntity): HttpRequest = withEntity(f(entity)) override def withMethod(method: akka.http.model.japi.HttpMethod): HttpRequest = copy(method = method.asInstanceOf[HttpMethod]) override def withProtocol(protocol: akka.http.model.japi.HttpProtocol): HttpRequest = copy(protocol = protocol.asInstanceOf[HttpProtocol]) @@ -292,7 +284,7 @@ object HttpRequest { */ final case class HttpResponse(status: StatusCode = StatusCodes.OK, headers: immutable.Seq[HttpHeader] = Nil, - entity: HttpEntity = HttpEntity.Empty, + entity: ResponseEntity = HttpEntity.Empty, protocol: HttpProtocol = HttpProtocols.`HTTP/1.1`) extends japi.HttpResponse with HttpMessage { type Self = HttpResponse @@ -302,12 +294,15 @@ final case class HttpResponse(status: StatusCode = StatusCodes.OK, override def withHeaders(headers: immutable.Seq[HttpHeader]) = if (headers eq this.headers) this else copy(headers = headers) - override def withEntity(entity: japi.HttpEntity) = if (entity eq this.entity) this else copy(entity = entity.asInstanceOf[HttpEntity]) - - override def withHeadersAndEntity(headers: immutable.Seq[HttpHeader], entity: HttpEntity) = - if ((headers eq this.headers) && (entity eq this.entity)) this else copy(headers = headers, entity = entity) - override def withProtocol(protocol: akka.http.model.japi.HttpProtocol): akka.http.model.japi.HttpResponse = copy(protocol = protocol.asInstanceOf[HttpProtocol]) override def withStatus(statusCode: Int): akka.http.model.japi.HttpResponse = copy(status = statusCode) override def withStatus(statusCode: akka.http.model.japi.StatusCode): akka.http.model.japi.HttpResponse = copy(status = statusCode.asInstanceOf[StatusCode]) + + override def withHeadersAndEntity(headers: immutable.Seq[HttpHeader], entity: MessageEntity): HttpResponse = withHeadersAndEntity(headers, entity: ResponseEntity) + def withHeadersAndEntity(headers: immutable.Seq[HttpHeader], entity: ResponseEntity): HttpResponse = copy(headers = headers, entity = entity) + override def withEntity(entity: japi.ResponseEntity): HttpResponse = copy(entity = entity.asInstanceOf[ResponseEntity]) + override def withEntity(entity: MessageEntity): HttpResponse = copy(entity = entity) + override def withEntity(entity: japi.RequestEntity): HttpResponse = withEntity(entity: japi.ResponseEntity) + + def mapEntity(f: ResponseEntity ⇒ ResponseEntity): HttpResponse = withEntity(f(entity)) } \ No newline at end of file diff --git a/akka-http-core/src/main/scala/akka/http/model/MultipartContent.scala b/akka-http-core/src/main/scala/akka/http/model/MultipartContent.scala index fbeef269c3..b6ec3ef271 100644 --- a/akka-http-core/src/main/scala/akka/http/model/MultipartContent.scala +++ b/akka-http-core/src/main/scala/akka/http/model/MultipartContent.scala @@ -85,16 +85,16 @@ object MultipartFormData { } } -final case class FormFile(name: Option[String], entity: HttpEntity) +final case class FormFile(name: Option[String], entity: BodyPartEntity) object FormFile { - def apply(name: String, entity: HttpEntity): FormFile = apply(Some(name), entity) + def apply(name: String, entity: BodyPartEntity): FormFile = apply(Some(name), entity) } /** * Model for one part of a multipart message. */ -final case class BodyPart(entity: HttpEntity, headers: immutable.Seq[HttpHeader] = Nil) { +final case class BodyPart(entity: BodyPartEntity, headers: immutable.Seq[HttpHeader] = Nil) { val name: Option[String] = dispositionParameterValue("name") def filename: Option[String] = dispositionParameterValue("filename") @@ -126,8 +126,8 @@ object BodyPart { case None ⇒ apply(formFile.entity, fieldName) } - def apply(entity: HttpEntity, fieldName: String): BodyPart = apply(entity, fieldName, Map.empty[String, String]) - def apply(entity: HttpEntity, fieldName: String, params: Map[String, String]): BodyPart = + def apply(entity: BodyPartEntity, fieldName: String): BodyPart = apply(entity, fieldName, Map.empty[String, String]) + def apply(entity: BodyPartEntity, fieldName: String, params: Map[String, String]): BodyPart = BodyPart(entity, immutable.Seq(`Content-Disposition`(ContentDispositionTypes.`form-data`, params.updated("name", fieldName)))) } @@ -144,7 +144,7 @@ object BodyPart { * }}} */ object NamedBodyPart { - def unapply(part: BodyPart): Option[(String, HttpEntity, immutable.Seq[HttpHeader])] = + def unapply(part: BodyPart): Option[(String, BodyPartEntity, immutable.Seq[HttpHeader])] = part.name.map(name ⇒ (name, part.entity, part.headers)) } @@ -162,6 +162,6 @@ object NamedBodyPart { * }}} */ object FileBodyPart { - def unapply(part: BodyPart): Option[(String, String, HttpEntity, immutable.Seq[HttpHeader])] = + def unapply(part: BodyPart): Option[(String, String, BodyPartEntity, immutable.Seq[HttpHeader])] = part.filename.map(filename ⇒ (part.name.getOrElse(""), filename, part.entity, part.headers)) } diff --git a/akka-http-core/src/main/scala/akka/http/model/japi/JavaMapping.scala b/akka-http-core/src/main/scala/akka/http/model/japi/JavaMapping.scala index a75f8b31b9..e032e535e2 100644 --- a/akka-http-core/src/main/scala/akka/http/model/japi/JavaMapping.scala +++ b/akka-http-core/src/main/scala/akka/http/model/japi/JavaMapping.scala @@ -110,7 +110,6 @@ object JavaMapping { implicit object HttpCharset extends Inherited[HttpCharset, model.HttpCharset] implicit object HttpCharsetRange extends Inherited[HttpCharsetRange, model.HttpCharsetRange] implicit object HttpEntity extends Inherited[HttpEntity, model.HttpEntity] - implicit object HttpEntityRegular extends Inherited[HttpEntityRegular, model.HttpEntity.Regular] implicit object HttpHeader extends Inherited[HttpHeader, model.HttpHeader] implicit object HttpMethod extends Inherited[HttpMethod, model.HttpMethod] implicit object HttpProtocol extends Inherited[HttpProtocol, model.HttpProtocol] diff --git a/akka-http-core/src/main/scala/akka/http/model/package.scala b/akka-http-core/src/main/scala/akka/http/model/package.scala new file mode 100644 index 0000000000..7a7b82814d --- /dev/null +++ b/akka-http-core/src/main/scala/akka/http/model/package.scala @@ -0,0 +1,10 @@ +/* + * Copyright (C) 2009-2014 Typesafe Inc. + */ + +package akka.http + +package object model { + /** An entity that can be used for every HttpMessage, i.e. for requests and responses. */ + type MessageEntity = RequestEntity +} diff --git a/akka-http-core/src/test/scala/akka/http/engine/parsing/RequestParserSpec.scala b/akka-http-core/src/test/scala/akka/http/engine/parsing/RequestParserSpec.scala index e567302996..384f66e51b 100644 --- a/akka-http-core/src/test/scala/akka/http/engine/parsing/RequestParserSpec.scala +++ b/akka-http-core/src/test/scala/akka/http/engine/parsing/RequestParserSpec.scala @@ -377,7 +377,7 @@ class RequestParserSpec extends FreeSpec with Matchers with BeforeAndAfterAll { private def newParser = new HttpRequestParser(ParserSettings(system), false)() - private def compactEntity(entity: HttpEntity): Deferrable[HttpEntity] = + private def compactEntity(entity: RequestEntity): Deferrable[RequestEntity] = entity match { case x: Chunked ⇒ compactEntityChunks(x.chunks).map(compacted ⇒ x.copy(chunks = compacted)) case _ ⇒ entity.toStrict(250.millis) diff --git a/akka-http-core/src/test/scala/akka/http/engine/parsing/ResponseParserSpec.scala b/akka-http-core/src/test/scala/akka/http/engine/parsing/ResponseParserSpec.scala index e1551686fb..3f978c60b1 100644 --- a/akka-http-core/src/test/scala/akka/http/engine/parsing/ResponseParserSpec.scala +++ b/akka-http-core/src/test/scala/akka/http/engine/parsing/ResponseParserSpec.scala @@ -239,7 +239,7 @@ class ResponseParserSpec extends FreeSpec with Matchers with BeforeAndAfterAll { parser } - private def compactEntity(entity: HttpEntity): Deferrable[HttpEntity] = + private def compactEntity(entity: ResponseEntity): Deferrable[ResponseEntity] = entity match { case x: HttpEntity.Chunked ⇒ compactEntityChunks(x.chunks).map(compacted ⇒ x.copy(chunks = compacted)) case _ ⇒ entity.toStrict(250.millis) diff --git a/akka-http-testkit/src/main/scala/akka/http/testkit/RouteTestResultComponent.scala b/akka-http-testkit/src/main/scala/akka/http/testkit/RouteTestResultComponent.scala index 214d8d1009..3f84c9dc1d 100644 --- a/akka-http-testkit/src/main/scala/akka/http/testkit/RouteTestResultComponent.scala +++ b/akka-http-testkit/src/main/scala/akka/http/testkit/RouteTestResultComponent.scala @@ -39,7 +39,7 @@ trait RouteTestResultComponent { def response: HttpResponse = rawResponse.copy(entity = entity) /** Returns a "fresh" entity with a "fresh" unconsumed byte- or chunk stream (if not strict) */ - def entity: HttpEntity = entityRecreator() + def entity: ResponseEntity = entityRecreator() def chunks: immutable.Seq[ChunkStreamPart] = entity match { @@ -76,7 +76,7 @@ trait RouteTestResultComponent { this } - private[this] lazy val entityRecreator: () ⇒ HttpEntity = + private[this] lazy val entityRecreator: () ⇒ ResponseEntity = rawResponse.entity match { case s: HttpEntity.Strict ⇒ () ⇒ s diff --git a/akka-http-tests/src/test/scala/akka/http/marshalling/MarshallingSpec.scala b/akka-http-tests/src/test/scala/akka/http/marshalling/MarshallingSpec.scala index 03541ae596..8f69888bf5 100644 --- a/akka-http-tests/src/test/scala/akka/http/marshalling/MarshallingSpec.scala +++ b/akka-http-tests/src/test/scala/akka/http/marshalling/MarshallingSpec.scala @@ -42,6 +42,7 @@ class MarshallingSpec extends FreeSpec with Matchers with BeforeAndAfterAll with "The GenericMarshallers." - { "optionMarshaller should enable marshalling of Option[T]" in { + marshal(Some("Ha“llo")) shouldEqual HttpEntity("Ha“llo") marshal(None: Option[String]) shouldEqual HttpEntity.Empty } diff --git a/akka-http/src/main/scala/akka/http/client/RequestBuilding.scala b/akka-http/src/main/scala/akka/http/client/RequestBuilding.scala index 5328c0f539..a31bb93170 100644 --- a/akka-http/src/main/scala/akka/http/client/RequestBuilding.scala +++ b/akka-http/src/main/scala/akka/http/client/RequestBuilding.scala @@ -32,7 +32,7 @@ trait RequestBuilding extends TransformerPipelineSupport { def apply[T](uri: String, content: Option[T])(implicit m: ToEntityMarshallers[T], ec: ExecutionContext): HttpRequest = apply(Uri(uri), content) - def apply(uri: String, entity: HttpEntity.Regular): HttpRequest = + def apply(uri: String, entity: RequestEntity): HttpRequest = apply(Uri(uri), entity) def apply(uri: Uri): HttpRequest = @@ -45,11 +45,11 @@ trait RequestBuilding extends TransformerPipelineSupport { content match { case None ⇒ apply(uri, HttpEntity.Empty) case Some(value) ⇒ - val entity = Marshal(value).to[HttpEntity.Regular].await(timeout.duration) + val entity = Marshal(value).to[RequestEntity].await(timeout.duration) apply(uri, entity) } - def apply(uri: Uri, entity: HttpEntity.Regular): HttpRequest = + def apply(uri: Uri, entity: RequestEntity): HttpRequest = HttpRequest(method, uri, Nil, entity) } diff --git a/akka-http/src/main/scala/akka/http/marshalling/EmptyValue.scala b/akka-http/src/main/scala/akka/http/marshalling/EmptyValue.scala index 958d2ae70f..0363746db3 100644 --- a/akka-http/src/main/scala/akka/http/marshalling/EmptyValue.scala +++ b/akka-http/src/main/scala/akka/http/marshalling/EmptyValue.scala @@ -10,7 +10,7 @@ import akka.http.model._ class EmptyValue[+T] private (val emptyValue: T) object EmptyValue { - implicit def emptyEntity = new EmptyValue[HttpEntity.Regular](HttpEntity.Empty) - implicit val emptyHeadersAndEntity = new EmptyValue[(immutable.Seq[HttpHeader], HttpEntity.Regular)](Nil -> HttpEntity.Empty) + implicit def emptyEntity = new EmptyValue[UniversalEntity](HttpEntity.Empty) + implicit val emptyHeadersAndEntity = new EmptyValue[(immutable.Seq[HttpHeader], UniversalEntity)](Nil -> HttpEntity.Empty) implicit val emptyResponse = new EmptyValue[HttpResponse](HttpResponse(entity = emptyEntity.emptyValue)) } \ No newline at end of file diff --git a/akka-http/src/main/scala/akka/http/marshalling/Marshallers.scala b/akka-http/src/main/scala/akka/http/marshalling/Marshallers.scala index 6269b25df5..a51ebef5c5 100644 --- a/akka-http/src/main/scala/akka/http/marshalling/Marshallers.scala +++ b/akka-http/src/main/scala/akka/http/marshalling/Marshallers.scala @@ -30,7 +30,7 @@ object Marshallers extends SingleMarshallerMarshallers { Marshallers(`text/xml`, `application/xml`, `text/html`, `application/xhtml+xml`)(PredefinedToEntityMarshallers.nodeSeqMarshaller) } - implicit def entity2response[T](implicit m: Marshallers[T, HttpEntity], ec: ExecutionContext): Marshallers[T, HttpResponse] = + implicit def entity2response[T](implicit m: Marshallers[T, ResponseEntity], ec: ExecutionContext): Marshallers[T, HttpResponse] = m map (entity ⇒ HttpResponse(entity = entity)) } diff --git a/akka-http/src/main/scala/akka/http/marshalling/MediaTypeOverrider.scala b/akka-http/src/main/scala/akka/http/marshalling/MediaTypeOverrider.scala index dc9fcd8bbc..32620275a3 100644 --- a/akka-http/src/main/scala/akka/http/marshalling/MediaTypeOverrider.scala +++ b/akka-http/src/main/scala/akka/http/marshalling/MediaTypeOverrider.scala @@ -21,10 +21,10 @@ object MediaTypeOverrider { } implicit val forResponse = new MediaTypeOverrider[HttpResponse] { def apply(value: HttpResponse, mediaType: MediaType) = - value.mapEntity(forEntity(_: HttpEntity, mediaType)) + value.mapEntity(forEntity(_: ResponseEntity, mediaType)) } implicit val forRequest = new MediaTypeOverrider[HttpRequest] { def apply(value: HttpRequest, mediaType: MediaType) = - value.mapEntity(forEntity(_, mediaType)) + value.mapEntity(forEntity(_: RequestEntity, mediaType)) } } diff --git a/akka-http/src/main/scala/akka/http/marshalling/PredefinedToEntityMarshallers.scala b/akka-http/src/main/scala/akka/http/marshalling/PredefinedToEntityMarshallers.scala index 8c445e7b53..657f034dd7 100644 --- a/akka-http/src/main/scala/akka/http/marshalling/PredefinedToEntityMarshallers.scala +++ b/akka-http/src/main/scala/akka/http/marshalling/PredefinedToEntityMarshallers.scala @@ -61,7 +61,7 @@ trait PredefinedToEntityMarshallers extends MultipartMarshallers { HttpEntity(ContentType(`application/x-www-form-urlencoded`, charset), string) } - implicit val HttpEntityMarshaller: ToEntityMarshaller[HttpEntity.Regular] = Marshaller { value ⇒ + implicit val HttpEntityMarshaller: ToEntityMarshaller[MessageEntity] = Marshaller { value ⇒ // since we don't want to recode we simply ignore the charset determined by content negotiation here Deferrable(Marshalling.WithOpenCharset(value.contentType.mediaType, _ ⇒ value)) } diff --git a/akka-http/src/main/scala/akka/http/marshalling/package.scala b/akka-http/src/main/scala/akka/http/marshalling/package.scala index 0b55424394..2eec53b722 100644 --- a/akka-http/src/main/scala/akka/http/marshalling/package.scala +++ b/akka-http/src/main/scala/akka/http/marshalling/package.scala @@ -8,12 +8,12 @@ import scala.collection.immutable import akka.http.model._ package object marshalling { - type ToEntityMarshaller[T] = Marshaller[T, HttpEntity.Regular] - type ToHeadersAndEntityMarshaller[T] = Marshaller[T, (immutable.Seq[HttpHeader], HttpEntity.Regular)] + type ToEntityMarshaller[T] = Marshaller[T, MessageEntity] + type ToHeadersAndEntityMarshaller[T] = Marshaller[T, (immutable.Seq[HttpHeader], MessageEntity)] type ToResponseMarshaller[T] = Marshaller[T, HttpResponse] type ToRequestMarshaller[T] = Marshaller[T, HttpRequest] - type ToEntityMarshallers[T] = Marshallers[T, HttpEntity.Regular] + type ToEntityMarshallers[T] = Marshallers[T, MessageEntity] type ToResponseMarshallers[T] = Marshallers[T, HttpResponse] type ToRequestMarshallers[T] = Marshallers[T, HttpRequest] } diff --git a/akka-http/src/main/scala/akka/http/server/RequestContext.scala b/akka-http/src/main/scala/akka/http/server/RequestContext.scala index 2c480fb2bb..e7ccd74ae4 100644 --- a/akka-http/src/main/scala/akka/http/server/RequestContext.scala +++ b/akka-http/src/main/scala/akka/http/server/RequestContext.scala @@ -106,7 +106,7 @@ trait RequestContext { /** * Returns a copy of this context with the given response transformation function chained into the response chain. */ - def withHttpResponseEntityMapped(f: HttpEntity ⇒ HttpEntity): RequestContext + def withHttpResponseEntityMapped(f: ResponseEntity ⇒ ResponseEntity): RequestContext /** * Returns a copy of this context with the given response transformation function chained into the response chain. diff --git a/akka-http/src/main/scala/akka/http/server/RequestContextImpl.scala b/akka-http/src/main/scala/akka/http/server/RequestContextImpl.scala index 284535e927..d2691d0cab 100644 --- a/akka-http/src/main/scala/akka/http/server/RequestContextImpl.scala +++ b/akka-http/src/main/scala/akka/http/server/RequestContextImpl.scala @@ -72,7 +72,7 @@ private[http] class RequestContextImpl( case RouteResult.Complete(response) ⇒ RouteResult.complete(f(response)) } - override def withHttpResponseEntityMapped(f: HttpEntity ⇒ HttpEntity): RequestContext = + override def withHttpResponseEntityMapped(f: ResponseEntity ⇒ ResponseEntity): RequestContext = withHttpResponseMapped(_ mapEntity f) override def withHttpResponseHeadersMapped(f: immutable.Seq[HttpHeader] ⇒ immutable.Seq[HttpHeader]): RequestContext = diff --git a/akka-http/src/main/scala/akka/http/server/directives/BasicDirectives.scala b/akka-http/src/main/scala/akka/http/server/directives/BasicDirectives.scala index 85eeb06560..e58c2e4e1b 100644 --- a/akka-http/src/main/scala/akka/http/server/directives/BasicDirectives.scala +++ b/akka-http/src/main/scala/akka/http/server/directives/BasicDirectives.scala @@ -33,7 +33,7 @@ trait BasicDirectives { def mapHttpResponse(f: HttpResponse ⇒ HttpResponse): Directive0 = mapRequestContext(_ withHttpResponseMapped f) - def mapHttpResponseEntity(f: HttpEntity ⇒ HttpEntity): Directive0 = + def mapHttpResponseEntity(f: ResponseEntity ⇒ ResponseEntity): Directive0 = mapRequestContext(_ withHttpResponseEntityMapped f) def mapHttpResponseHeaders(f: immutable.Seq[HttpHeader] ⇒ immutable.Seq[HttpHeader]): Directive0 =