Permalink
Browse files

!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
  • Loading branch information...
jrudolph committed Sep 22, 2014
1 parent 6f68f87 commit c0ffee42598e6ec55df6fa91b929f0bee01a2e81
Showing with 289 additions and 159 deletions.
  1. +8 −0 akka-http-core/src/main/java/akka/http/model/japi/BodyPartEntity.java
  2. +70 −0 akka-http-core/src/main/java/akka/http/model/japi/HttpEntities.java
  3. +17 −49 akka-http-core/src/main/java/akka/http/model/japi/HttpEntity.java
  4. +1 −1 akka-http-core/src/main/java/akka/http/model/japi/HttpEntityChunked.java
  5. +1 −1 akka-http-core/src/main/java/akka/http/model/japi/HttpEntityCloseDelimited.java
  6. +1 −1 akka-http-core/src/main/java/akka/http/model/japi/HttpEntityDefault.java
  7. +15 −0 akka-http-core/src/main/java/akka/http/model/japi/HttpEntityIndefiniteLength.java
  8. +0 −10 akka-http-core/src/main/java/akka/http/model/japi/HttpEntityRegular.java
  9. +1 −1 akka-http-core/src/main/java/akka/http/model/japi/HttpEntityStrict.java
  10. +6 −6 akka-http-core/src/main/java/akka/http/model/japi/HttpMessage.java
  11. +6 −1 akka-http-core/src/main/java/akka/http/model/japi/HttpRequest.java
  12. +10 −0 akka-http-core/src/main/java/akka/http/model/japi/HttpResponse.java
  13. +8 −0 akka-http-core/src/main/java/akka/http/model/japi/RequestEntity.java
  14. +8 −0 akka-http-core/src/main/java/akka/http/model/japi/ResponseEntity.java
  15. +8 −0 akka-http-core/src/main/java/akka/http/model/japi/UniversalEntity.java
  16. +2 −2 akka-http-core/src/main/scala/akka/http/engine/parsing/BodyPartParser.scala
  17. +4 −4 akka-http-core/src/main/scala/akka/http/engine/parsing/HttpMessageParser.scala
  18. +1 −1 akka-http-core/src/main/scala/akka/http/engine/parsing/HttpRequestParser.scala
  19. +1 −1 akka-http-core/src/main/scala/akka/http/engine/parsing/HttpResponseParser.scala
  20. +2 −2 akka-http-core/src/main/scala/akka/http/engine/parsing/ParserOutput.scala
  21. +4 −7 akka-http-core/src/main/scala/akka/http/engine/rendering/BodyPartRenderer.scala
  22. +1 −1 akka-http-core/src/main/scala/akka/http/engine/rendering/HttpResponseRendererFactory.scala
  23. +59 −21 akka-http-core/src/main/scala/akka/http/model/HttpEntity.scala
  24. +18 −23 akka-http-core/src/main/scala/akka/http/model/HttpMessage.scala
  25. +7 −7 akka-http-core/src/main/scala/akka/http/model/MultipartContent.scala
  26. +0 −1 akka-http-core/src/main/scala/akka/http/model/japi/JavaMapping.scala
  27. +10 −0 akka-http-core/src/main/scala/akka/http/model/package.scala
  28. +1 −1 akka-http-core/src/test/scala/akka/http/engine/parsing/RequestParserSpec.scala
  29. +1 −1 akka-http-core/src/test/scala/akka/http/engine/parsing/ResponseParserSpec.scala
  30. +2 −2 akka-http-testkit/src/main/scala/akka/http/testkit/RouteTestResultComponent.scala
  31. +1 −0 akka-http-tests/src/test/scala/akka/http/marshalling/MarshallingSpec.scala
  32. +3 −3 akka-http/src/main/scala/akka/http/client/RequestBuilding.scala
  33. +2 −2 akka-http/src/main/scala/akka/http/marshalling/EmptyValue.scala
  34. +1 −1 akka-http/src/main/scala/akka/http/marshalling/Marshallers.scala
  35. +2 −2 akka-http/src/main/scala/akka/http/marshalling/MediaTypeOverrider.scala
  36. +1 −1 akka-http/src/main/scala/akka/http/marshalling/PredefinedToEntityMarshallers.scala
  37. +3 −3 akka-http/src/main/scala/akka/http/marshalling/package.scala
  38. +1 −1 akka-http/src/main/scala/akka/http/server/RequestContext.scala
  39. +1 −1 akka-http/src/main/scala/akka/http/server/RequestContextImpl.scala
  40. +1 −1 akka-http/src/main/scala/akka/http/server/directives/BasicDirectives.scala
@@ -0,0 +1,8 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.model.japi;
/** Marker-interface for entity types that can be used in a body part */
public interface BodyPartEntity extends HttpEntity {}
@@ -0,0 +1,70 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
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<ByteString> data) {
return new akka.http.model.HttpEntity.Default((akka.http.model.ContentType) contentType, contentLength, data);
}
public static HttpEntityCloseDelimited createCloseDelimited(ContentType contentType, Publisher<ByteString> data) {
return new akka.http.model.HttpEntity.CloseDelimited((akka.http.model.ContentType) contentType, data);
}
public static HttpEntityIndefiniteLength createIndefiniteLength(ContentType contentType, Publisher<ByteString> data) {
return new akka.http.model.HttpEntity.IndefiniteLength((akka.http.model.ContentType) contentType, data);
}
public static HttpEntityChunked createChunked(ContentType contentType, Publisher<ChunkStreamPart> chunks) {
return new akka.http.model.HttpEntity.Chunked(
(akka.http.model.ContentType) contentType,
Util.<ChunkStreamPart, akka.http.model.HttpEntity.ChunkStreamPart>upcastPublisher(chunks));
}
public static HttpEntityChunked createChunked(ContentType contentType, Publisher<ByteString> data, FlowMaterializer materializer) {
return akka.http.model.HttpEntity.Chunked$.MODULE$.fromData(
(akka.http.model.ContentType) contentType,
data, materializer);
}
}
@@ -9,8 +9,6 @@
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 @@
* 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
*
* 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
*
* 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.
* 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 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 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<ByteString> 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<ByteString> data) {
return new akka.http.model.HttpEntity.Default((akka.http.model.ContentType) contentType, contentLength, data);
}
public static HttpEntityCloseDelimited createCloseDelimited(ContentType contentType, Publisher<ByteString> data) {
return new akka.http.model.HttpEntity.CloseDelimited((akka.http.model.ContentType) contentType, data);
}
public static HttpEntityChunked createChunked(ContentType contentType, Publisher<ChunkStreamPart> chunks) {
return new akka.http.model.HttpEntity.Chunked(
(akka.http.model.ContentType) contentType,
Util.<ChunkStreamPart, akka.http.model.HttpEntity.ChunkStreamPart>upcastPublisher(chunks));
}
public static HttpEntityChunked createChunked(ContentType contentType, Publisher<ByteString> data, FlowMaterializer materializer) {
return akka.http.model.HttpEntity.Chunked$.MODULE$.fromData(
(akka.http.model.ContentType) contentType,
data, materializer);
}
}
@@ -10,6 +10,6 @@
* 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<ChunkStreamPart> getChunks();
}
@@ -12,6 +12,6 @@
* 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<ByteString> data();
}
@@ -10,7 +10,7 @@
/**
* 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<ByteString> data();
}
@@ -0,0 +1,15 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
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<ByteString> data();
}

This file was deleted.

Oops, something went wrong.
@@ -9,6 +9,6 @@
/**
* 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();
}
@@ -48,7 +48,7 @@
/**
* The entity of this message.
*/
HttpEntity entity();
ResponseEntity entity();
public static interface MessageTransformations<Self> {
/**
@@ -71,11 +71,6 @@
*/
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 @@
* 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);
}
}
@@ -21,7 +21,7 @@
/**
* 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 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.
*/
@@ -13,6 +13,11 @@
*/
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 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.
*/
@@ -0,0 +1,8 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.model.japi;
/** Marker-interface for entity types that can be used in a request */
public interface RequestEntity extends ResponseEntity {}
@@ -0,0 +1,8 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.model.japi;
/** Marker-interface for entity types that can be used in a response */
public interface ResponseEntity extends HttpEntity {}
@@ -0,0 +1,8 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.model.japi;
/** Marker-interface for entity types that can be used in any context */
public interface UniversalEntity extends RequestEntity, ResponseEntity, BodyPartEntity {}
@@ -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
@@ -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)
}
@@ -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 {
Oops, something went wrong.

0 comments on commit c0ffee4

Please sign in to comment.