Skip to content
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 c0ffee4
Show file tree
Hide file tree
Showing 40 changed files with 289 additions and 159 deletions.
@@ -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 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<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.

@@ -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 @@ public interface HttpMessage {
/**
* The entity of this message.
*/
HttpEntity entity();
ResponseEntity entity();

public static interface MessageTransformations<Self> {
/**
@@ -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);
}
}
@@ -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.
*/
@@ -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.
*/
@@ -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 {

0 comments on commit c0ffee4

Please sign in to comment.