Merge pull request #19066 from spray/wip-19034-mathias
http: refactor MediaType / ContentType model and content negotiation
This commit is contained in:
commit
0fbe4d73dc
80 changed files with 1649 additions and 1294 deletions
|
|
@ -35,7 +35,7 @@ public class ModelDocTest {
|
|||
Authorization authorization = Authorization.basic("user", "pass");
|
||||
HttpRequest complexRequest =
|
||||
HttpRequest.PUT("/user")
|
||||
.withEntity(HttpEntities.create(MediaTypes.TEXT_PLAIN.toContentType(), "abc"))
|
||||
.withEntity(HttpEntities.create(ContentTypes.TEXT_PLAIN_UTF8, "abc"))
|
||||
.addHeader(authorization)
|
||||
.withProtocol(HttpProtocols.HTTP_1_0);
|
||||
//#construct-request
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ public class HighLevelServerExample extends HttpApp {
|
|||
// matches the empty path
|
||||
pathSingleSlash().route(
|
||||
// return a constant string with a certain content type
|
||||
complete(ContentTypes.TEXT_HTML,
|
||||
complete(ContentTypes.TEXT_HTML_UTF8,
|
||||
"<html><body>Hello world!</body></html>")
|
||||
),
|
||||
path("ping").route(
|
||||
|
|
|
|||
|
|
@ -156,7 +156,7 @@ public class HttpServerExampleDocTest {
|
|||
.via(failureDetection)
|
||||
.map(request -> {
|
||||
Source<ByteString, Object> bytes = request.entity().getDataBytes();
|
||||
HttpEntity.Chunked entity = HttpEntities.create(ContentTypes.TEXT_PLAIN, bytes);
|
||||
HttpEntityChunked entity = HttpEntities.create(ContentTypes.TEXT_PLAIN_UTF8, bytes);
|
||||
|
||||
return HttpResponse.create()
|
||||
.withEntity(entity);
|
||||
|
|
@ -199,7 +199,7 @@ public class HttpServerExampleDocTest {
|
|||
if (uri.path().equals("/"))
|
||||
return
|
||||
HttpResponse.create()
|
||||
.withEntity(ContentTypes.TEXT_HTML,
|
||||
.withEntity(ContentTypes.TEXT_HTML_UTF8,
|
||||
"<html><body>Hello world!</body></html>");
|
||||
else if (uri.path().equals("/hello")) {
|
||||
String name = Util.getOrElse(uri.query().get("name"), "Mister X");
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ class HttpServerExampleSpec extends WordSpec with Matchers {
|
|||
.via(reactToConnectionFailure)
|
||||
.map { request =>
|
||||
// simple text "echo" response:
|
||||
HttpResponse(entity = HttpEntity(ContentTypes.`text/plain`, request.entity.dataBytes))
|
||||
HttpResponse(entity = HttpEntity(ContentTypes.`text/plain(UTF-8)`, request.entity.dataBytes))
|
||||
}
|
||||
|
||||
serverSource
|
||||
|
|
@ -173,7 +173,7 @@ class HttpServerExampleSpec extends WordSpec with Matchers {
|
|||
|
||||
val requestHandler: HttpRequest => HttpResponse = {
|
||||
case HttpRequest(GET, Uri.Path("/"), _, _, _) =>
|
||||
HttpResponse(entity = HttpEntity(MediaTypes.`text/html`,
|
||||
HttpResponse(entity = HttpEntity(ContentTypes.`text/html(UTF-8)`,
|
||||
"<html><body>Hello world!</body></html>"))
|
||||
|
||||
case HttpRequest(GET, Uri.Path("/ping"), _, _, _) =>
|
||||
|
|
@ -207,7 +207,7 @@ class HttpServerExampleSpec extends WordSpec with Matchers {
|
|||
|
||||
val requestHandler: HttpRequest => HttpResponse = {
|
||||
case HttpRequest(GET, Uri.Path("/"), _, _, _) =>
|
||||
HttpResponse(entity = HttpEntity(MediaTypes.`text/html`,
|
||||
HttpResponse(entity = HttpEntity(ContentTypes.`text/html(UTF-8)`,
|
||||
"<html><body>Hello world!</body></html>"))
|
||||
|
||||
case HttpRequest(GET, Uri.Path("/ping"), _, _, _) =>
|
||||
|
|
|
|||
|
|
@ -32,12 +32,13 @@ class ModelSpec extends AkkaSpec {
|
|||
// customize every detail of HTTP request
|
||||
import HttpProtocols._
|
||||
import MediaTypes._
|
||||
import HttpCharsets._
|
||||
val userData = ByteString("abc")
|
||||
val authorization = headers.Authorization(BasicHttpCredentials("user", "pass"))
|
||||
HttpRequest(
|
||||
PUT,
|
||||
uri = "/user",
|
||||
entity = HttpEntity(`text/plain`, userData),
|
||||
entity = HttpEntity(`text/plain` withCharset `UTF-8`, userData),
|
||||
headers = List(authorization),
|
||||
protocol = `HTTP/1.0`)
|
||||
//#construct-request
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ class CodingDirectivesExamplesSpec extends RoutingSpec {
|
|||
Get("/") ~> route ~> check {
|
||||
responseAs[String] shouldEqual "content"
|
||||
}
|
||||
Get("/") ~> `Accept-Encoding`(`identity;q=MIN`) ~> route ~> check {
|
||||
Get("/") ~> `Accept-Encoding`(deflate) ~> route ~> check {
|
||||
rejection shouldEqual UnacceptedResponseEncodingRejection(gzip)
|
||||
}
|
||||
}
|
||||
|
|
@ -48,9 +48,6 @@ class CodingDirectivesExamplesSpec extends RoutingSpec {
|
|||
Get("/") ~> route ~> check {
|
||||
response should haveContentEncoding(gzip)
|
||||
}
|
||||
Get("/") ~> `Accept-Encoding`() ~> route ~> check {
|
||||
rejection shouldEqual UnacceptedResponseEncodingRejection(gzip)
|
||||
}
|
||||
Get("/") ~> `Accept-Encoding`(gzip, deflate) ~> route ~> check {
|
||||
response should haveContentEncoding(gzip)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,15 +3,11 @@
|
|||
*/
|
||||
package docs.http.scaladsl.server.directives
|
||||
|
||||
import java.io.File
|
||||
|
||||
import akka.http.scaladsl.model.{ MediaTypes, HttpEntity, Multipart, StatusCodes }
|
||||
import akka.http.scaladsl.model._
|
||||
import akka.stream.io.Framing
|
||||
import akka.util.ByteString
|
||||
import docs.http.scaladsl.server.RoutingSpec
|
||||
|
||||
import scala.concurrent.Future
|
||||
import scala.util.{ Success, Failure }
|
||||
|
||||
class FileUploadDirectivesExamplesSpec extends RoutingSpec {
|
||||
|
||||
|
|
@ -32,7 +28,7 @@ class FileUploadDirectivesExamplesSpec extends RoutingSpec {
|
|||
Multipart.FormData(
|
||||
Multipart.FormData.BodyPart.Strict(
|
||||
"csv",
|
||||
HttpEntity(MediaTypes.`text/plain`, "1,5,7\n11,13,17"),
|
||||
HttpEntity(ContentTypes.`text/plain(UTF-8)`, "1,5,7\n11,13,17"),
|
||||
Map("filename" -> "data.csv")))
|
||||
|
||||
Post("/", multipartForm) ~> route ~> check {
|
||||
|
|
@ -68,7 +64,7 @@ class FileUploadDirectivesExamplesSpec extends RoutingSpec {
|
|||
val multipartForm =
|
||||
Multipart.FormData(Multipart.FormData.BodyPart.Strict(
|
||||
"csv",
|
||||
HttpEntity(MediaTypes.`text/plain`, "2,3,5\n7,11,13,17,23\n29,31,37\n"),
|
||||
HttpEntity(ContentTypes.`text/plain(UTF-8)`, "2,3,5\n7,11,13,17,23\n29,31,37\n"),
|
||||
Map("filename" -> "primes.csv")))
|
||||
|
||||
Post("/", multipartForm) ~> route ~> check {
|
||||
|
|
|
|||
|
|
@ -9,33 +9,33 @@ import akka.japi.Option;
|
|||
/**
|
||||
* Represents an Http content-type. A content-type consists of a media-type and an optional charset.
|
||||
*/
|
||||
public abstract class ContentType {
|
||||
/**
|
||||
* Returns the media-type of this content-type.
|
||||
*/
|
||||
public abstract MediaType mediaType();
|
||||
public interface ContentType {
|
||||
|
||||
/**
|
||||
* Returns the charset of this content-type.
|
||||
* The media-type of this content-type.
|
||||
*/
|
||||
public abstract HttpCharset charset();
|
||||
MediaType mediaType();
|
||||
|
||||
/**
|
||||
* Returns the optionally defined charset of this content-type.
|
||||
* True if this ContentType is non-textual.
|
||||
*/
|
||||
public abstract Option<HttpCharset> getDefinedCharset();
|
||||
boolean binary();
|
||||
|
||||
/**
|
||||
* Creates a content-type from a media-type and a charset.
|
||||
* Returns the charset if this ContentType is non-binary.
|
||||
*/
|
||||
public static ContentType create(MediaType mediaType, HttpCharset charset) {
|
||||
return akka.http.scaladsl.model.ContentType.apply((akka.http.scaladsl.model.MediaType) mediaType, (akka.http.scaladsl.model.HttpCharset) charset);
|
||||
Option<HttpCharset> getCharsetOption();
|
||||
|
||||
interface Binary extends ContentType {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a content-type from a media-type without specifying a charset.
|
||||
*/
|
||||
public static ContentType create(MediaType mediaType) {
|
||||
return akka.http.scaladsl.model.ContentType.apply((akka.http.scaladsl.model.MediaType) mediaType);
|
||||
interface NonBinary extends ContentType {
|
||||
HttpCharset charset();
|
||||
}
|
||||
|
||||
interface WithFixedCharset extends NonBinary {
|
||||
}
|
||||
|
||||
interface WithCharset extends NonBinary {
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,9 +17,4 @@ public abstract class ContentTypeRange {
|
|||
* Returns true if this range includes the given content type.
|
||||
*/
|
||||
public abstract boolean matches(ContentType contentType);
|
||||
|
||||
/**
|
||||
* Returns a ContentType instance which fits this range.
|
||||
*/
|
||||
public abstract ContentType specimen();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
package akka.http.javadsl.model;
|
||||
|
||||
|
||||
import akka.http.scaladsl.model.ContentType$;
|
||||
|
||||
/**
|
||||
* Contains the set of predefined content-types for convenience.
|
||||
* <p>
|
||||
|
|
@ -11,14 +13,26 @@ package akka.http.javadsl.model;
|
|||
* you can obtain it from a {@link MediaType} by using: {@code MediaTypes.TEXT_HTML.toContentType()}
|
||||
*/
|
||||
public final class ContentTypes {
|
||||
public static final ContentType APPLICATION_JSON = MediaTypes.APPLICATION_JSON.toContentType();
|
||||
public static final ContentType APPLICATION_OCTET_STREAM = MediaTypes.APPLICATION_OCTET_STREAM.toContentType();
|
||||
public static final ContentType.WithFixedCharset APPLICATION_JSON = MediaTypes.APPLICATION_JSON.toContentType();
|
||||
public static final ContentType.Binary APPLICATION_OCTET_STREAM = MediaTypes.APPLICATION_OCTET_STREAM.toContentType();
|
||||
|
||||
public static final ContentType TEXT_PLAIN = MediaTypes.TEXT_PLAIN.toContentType();
|
||||
public static final ContentType TEXT_PLAIN_UTF8 = akka.http.scaladsl.model.ContentTypes.text$divplain$u0028UTF$minus8$u0029();
|
||||
public static final ContentType TEXT_HTML = MediaTypes.TEXT_HTML.toContentType();
|
||||
public static final ContentType TEXT_XML = MediaTypes.TEXT_XML.toContentType();
|
||||
public static final ContentType.WithCharset TEXT_PLAIN_UTF8 =
|
||||
akka.http.scaladsl.model.ContentTypes.text$divplain$u0028UTF$minus8$u0029();
|
||||
public static final ContentType.WithCharset TEXT_HTML_UTF8 =
|
||||
akka.http.scaladsl.model.ContentTypes.text$divhtml$u0028UTF$minus8$u0029();
|
||||
public static final ContentType.WithCharset TEXT_XML_UTF8 =
|
||||
akka.http.scaladsl.model.ContentTypes.text$divxml$u0028UTF$minus8$u0029();
|
||||
|
||||
public static final ContentType APPLICATION_X_WWW_FORM_URLENCODED = MediaTypes.APPLICATION_X_WWW_FORM_URLENCODED.toContentType();
|
||||
public static final ContentType MULTIPART_FORM_DATA = MediaTypes.MULTIPART_FORM_DATA.toContentType();
|
||||
public static ContentType.Binary create(MediaType.Binary mediaType) {
|
||||
return ContentType$.MODULE$.apply((akka.http.scaladsl.model.MediaType.Binary) mediaType);
|
||||
}
|
||||
|
||||
public static ContentType.WithFixedCharset create(MediaType.WithFixedCharset mediaType) {
|
||||
return ContentType$.MODULE$.apply((akka.http.scaladsl.model.MediaType.WithFixedCharset) mediaType);
|
||||
}
|
||||
|
||||
public static ContentType.WithCharset create(MediaType.WithOpenCharset mediaType, HttpCharset charset) {
|
||||
return ContentType$.MODULE$.apply((akka.http.scaladsl.model.MediaType.WithOpenCharset) mediaType,
|
||||
(akka.http.scaladsl.model.HttpCharset) charset);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ public final class FormData {
|
|||
* Converts this FormData to a RequestEntity using the given encoding.
|
||||
*/
|
||||
public RequestEntity toEntity(HttpCharset charset) {
|
||||
return HttpEntities.create(ContentType.create(MediaTypes.APPLICATION_X_WWW_FORM_URLENCODED, charset), fields.render(charset));
|
||||
return HttpEntities.create(ContentTypes.create(MediaTypes.APPLICATION_X_WWW_FORM_URLENCODED, charset), fields.render(charset));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -9,10 +9,6 @@ package akka.http.javadsl.model;
|
|||
* charset. {@link HttpCharsetRanges} contains static constructors for HttpCharsetRanges.
|
||||
*/
|
||||
public abstract class HttpCharsetRange {
|
||||
/**
|
||||
* Returns if this range matches all charsets.
|
||||
*/
|
||||
public abstract boolean matchesAll();
|
||||
|
||||
/**
|
||||
* The qValue for this range.
|
||||
|
|
|
|||
|
|
@ -28,8 +28,8 @@ public final class HttpEntities {
|
|||
return HttpEntity$.MODULE$.apply(bytes);
|
||||
}
|
||||
|
||||
public static HttpEntityStrict create(ContentType contentType, String string) {
|
||||
return HttpEntity$.MODULE$.apply((akka.http.scaladsl.model.ContentType) contentType, string);
|
||||
public static HttpEntityStrict create(ContentType.NonBinary contentType, String string) {
|
||||
return HttpEntity$.MODULE$.apply((akka.http.scaladsl.model.ContentType.NonBinary) contentType, string);
|
||||
}
|
||||
|
||||
public static HttpEntityStrict create(ContentType contentType, byte[] bytes) {
|
||||
|
|
@ -52,7 +52,7 @@ public final class HttpEntities {
|
|||
return new akka.http.scaladsl.model.HttpEntity.Default((akka.http.scaladsl.model.ContentType) contentType, contentLength, data.asScala());
|
||||
}
|
||||
|
||||
public static HttpEntity.Chunked create(ContentType contentType, Source<ByteString, Object> data) {
|
||||
public static HttpEntityChunked create(ContentType contentType, Source<ByteString, Object> data) {
|
||||
return HttpEntity.Chunked$.MODULE$.fromData((akka.http.scaladsl.model.ContentType) contentType, data.asScala());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,48 +40,48 @@ public interface HttpEntity {
|
|||
/**
|
||||
* Returns the content-type of this entity
|
||||
*/
|
||||
public abstract ContentType contentType();
|
||||
ContentType contentType();
|
||||
|
||||
/**
|
||||
* The empty entity.
|
||||
*/
|
||||
public static final HttpEntityStrict EMPTY = HttpEntity$.MODULE$.Empty();
|
||||
HttpEntityStrict EMPTY = HttpEntity$.MODULE$.Empty();
|
||||
|
||||
/**
|
||||
* Returns if this entity is known to be empty. Open-ended entity types like
|
||||
* HttpEntityChunked and HttpCloseDelimited will always return false here.
|
||||
*/
|
||||
public abstract boolean isKnownEmpty();
|
||||
boolean isKnownEmpty();
|
||||
|
||||
/**
|
||||
* Returns if this entity is a subtype of HttpEntityChunked.
|
||||
*/
|
||||
public abstract boolean isChunked();
|
||||
boolean isChunked();
|
||||
|
||||
/**
|
||||
* Returns if this entity is a subtype of HttpEntityDefault.
|
||||
*/
|
||||
public abstract boolean isDefault();
|
||||
boolean isDefault();
|
||||
|
||||
/**
|
||||
* Returns if this entity is a subtype of HttpEntityCloseDelimited.
|
||||
*/
|
||||
public abstract boolean isCloseDelimited();
|
||||
boolean isCloseDelimited();
|
||||
|
||||
/**
|
||||
* Returns if this entity is a subtype of HttpEntityIndefiniteLength.
|
||||
*/
|
||||
public abstract boolean isIndefiniteLength();
|
||||
boolean isIndefiniteLength();
|
||||
|
||||
/**
|
||||
* Returns Some(contentLength) if the length is defined and none otherwise.
|
||||
*/
|
||||
public abstract Option<Long> getContentLengthOption();
|
||||
Option<Long> getContentLengthOption();
|
||||
|
||||
/**
|
||||
* Returns a stream of data bytes this entity consists of.
|
||||
*/
|
||||
public abstract Source<ByteString, Object> getDataBytes();
|
||||
Source<ByteString, Object> getDataBytes();
|
||||
|
||||
/**
|
||||
* Returns a future of a strict entity that contains the same data as this entity
|
||||
|
|
@ -92,5 +92,5 @@ public interface HttpEntity {
|
|||
* Use getDataBytes and stream processing instead if the expected data is big or
|
||||
* is likely to take a long time.
|
||||
*/
|
||||
public abstract Future<HttpEntityStrict> toStrict(long timeoutMillis, Materializer materializer);
|
||||
Future<HttpEntityStrict> toStrict(long timeoutMillis, Materializer materializer);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ public interface HttpMessage {
|
|||
/**
|
||||
* Returns a copy of Self message with a new entity.
|
||||
*/
|
||||
Self withEntity(ContentType type, String string);
|
||||
Self withEntity(ContentType.NonBinary type, String string);
|
||||
|
||||
/**
|
||||
* Returns a copy of Self message with a new entity.
|
||||
|
|
|
|||
|
|
@ -7,33 +7,61 @@ package akka.http.javadsl.model;
|
|||
/**
|
||||
* Represents an Http media-type. A media-type consists of a main-type and a sub-type.
|
||||
*/
|
||||
public abstract class MediaType {
|
||||
/**
|
||||
* Returns the main-type of this media-type.
|
||||
*/
|
||||
public abstract String mainType();
|
||||
public interface MediaType {
|
||||
|
||||
/**
|
||||
* Returns the sub-type of this media-type.
|
||||
* The main-type of this media-type.
|
||||
*/
|
||||
public abstract String subType();
|
||||
String mainType();
|
||||
|
||||
/**
|
||||
* The sub-type of this media-type.
|
||||
*/
|
||||
String subType();
|
||||
|
||||
/**
|
||||
* True when this media-type is generally compressible.
|
||||
*/
|
||||
boolean isCompressible();
|
||||
|
||||
/**
|
||||
* True when this media-type is not character-based.
|
||||
*/
|
||||
boolean binary();
|
||||
|
||||
boolean isApplication();
|
||||
boolean isAudio();
|
||||
boolean isImage();
|
||||
boolean isMessage();
|
||||
boolean isMultipart();
|
||||
boolean isText();
|
||||
boolean isVideo();
|
||||
|
||||
/**
|
||||
* Creates a media-range from this media-type.
|
||||
*/
|
||||
public MediaRange toRange() {
|
||||
return MediaRanges.create(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a ContentType from this media-type
|
||||
*/
|
||||
public ContentType toContentType() { return ContentType.create(this); }
|
||||
MediaRange toRange();
|
||||
|
||||
/**
|
||||
* Creates a media-range from this media-type with a given qValue.
|
||||
*/
|
||||
public MediaRange toRange(float qValue) {
|
||||
return MediaRanges.create(this, qValue);
|
||||
MediaRange toRange(float qValue);
|
||||
|
||||
interface Binary extends MediaType {
|
||||
ContentType.Binary toContentType();
|
||||
}
|
||||
|
||||
interface NonBinary extends MediaType {
|
||||
}
|
||||
|
||||
interface WithFixedCharset extends NonBinary {
|
||||
ContentType.WithFixedCharset toContentType();
|
||||
}
|
||||
|
||||
interface WithOpenCharset extends NonBinary {
|
||||
ContentType.WithCharset toContentType(HttpCharset charset);
|
||||
}
|
||||
|
||||
interface Multipart extends WithOpenCharset {
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,189 +7,190 @@ package akka.http.javadsl.model;
|
|||
import akka.http.impl.util.Util;
|
||||
import akka.http.scaladsl.model.MediaTypes$;
|
||||
import akka.japi.Option;
|
||||
|
||||
import java.util.Map;
|
||||
import scala.collection.immutable.List;
|
||||
|
||||
/**
|
||||
* Contains the set of predefined media-types.
|
||||
*/
|
||||
public abstract class MediaTypes {
|
||||
public static final MediaType APPLICATION_ATOM_XML = akka.http.scaladsl.model.MediaTypes.application$divatom$plusxml();
|
||||
public static final MediaType APPLICATION_BASE64 = akka.http.scaladsl.model.MediaTypes.application$divbase64();
|
||||
public static final MediaType APPLICATION_EXCEL = akka.http.scaladsl.model.MediaTypes.application$divexcel();
|
||||
public static final MediaType APPLICATION_FONT_WOFF = akka.http.scaladsl.model.MediaTypes.application$divfont$minuswoff();
|
||||
public static final MediaType APPLICATION_GNUTAR = akka.http.scaladsl.model.MediaTypes.application$divgnutar();
|
||||
public static final MediaType APPLICATION_JAVA_ARCHIVE = akka.http.scaladsl.model.MediaTypes.application$divjava$minusarchive();
|
||||
public static final MediaType APPLICATION_JAVASCRIPT = akka.http.scaladsl.model.MediaTypes.application$divjavascript();
|
||||
public static final MediaType APPLICATION_JSON = akka.http.scaladsl.model.MediaTypes.application$divjson();
|
||||
public static final MediaType APPLICATION_JSON_PATCH_JSON = akka.http.scaladsl.model.MediaTypes.application$divjson$minuspatch$plusjson();
|
||||
public static final MediaType APPLICATION_LHA = akka.http.scaladsl.model.MediaTypes.application$divlha();
|
||||
public static final MediaType APPLICATION_LZX = akka.http.scaladsl.model.MediaTypes.application$divlzx();
|
||||
public static final MediaType APPLICATION_MSPOWERPOINT = akka.http.scaladsl.model.MediaTypes.application$divmspowerpoint();
|
||||
public static final MediaType APPLICATION_MSWORD = akka.http.scaladsl.model.MediaTypes.application$divmsword();
|
||||
public static final MediaType APPLICATION_OCTET_STREAM = akka.http.scaladsl.model.MediaTypes.application$divoctet$minusstream();
|
||||
public static final MediaType APPLICATION_PDF = akka.http.scaladsl.model.MediaTypes.application$divpdf();
|
||||
public static final MediaType APPLICATION_POSTSCRIPT = akka.http.scaladsl.model.MediaTypes.application$divpostscript();
|
||||
public static final MediaType APPLICATION_RSS_XML = akka.http.scaladsl.model.MediaTypes.application$divrss$plusxml();
|
||||
public static final MediaType APPLICATION_SOAP_XML = akka.http.scaladsl.model.MediaTypes.application$divsoap$plusxml();
|
||||
public static final MediaType APPLICATION_VND_API_JSON = akka.http.scaladsl.model.MediaTypes.application$divvnd$u002Eapi$plusjson();
|
||||
public static final MediaType APPLICATION_VND_GOOGLE_EARTH_KML_XML = akka.http.scaladsl.model.MediaTypes.application$divvnd$u002Egoogle$minusearth$u002Ekml$plusxml();
|
||||
public static final MediaType APPLICATION_VND_GOOGLE_EARTH_KMZ = akka.http.scaladsl.model.MediaTypes.application$divvnd$u002Egoogle$minusearth$u002Ekmz();
|
||||
public static final MediaType APPLICATION_VND_MS_FONTOBJECT = akka.http.scaladsl.model.MediaTypes.application$divvnd$u002Ems$minusfontobject();
|
||||
public static final MediaType APPLICATION_VND_OASIS_OPENDOCUMENT_CHART = akka.http.scaladsl.model.MediaTypes.application$divvnd$u002Eoasis$u002Eopendocument$u002Echart();
|
||||
public static final MediaType APPLICATION_VND_OASIS_OPENDOCUMENT_DATABASE = akka.http.scaladsl.model.MediaTypes.application$divvnd$u002Eoasis$u002Eopendocument$u002Edatabase();
|
||||
public static final MediaType APPLICATION_VND_OASIS_OPENDOCUMENT_FORMULA = akka.http.scaladsl.model.MediaTypes.application$divvnd$u002Eoasis$u002Eopendocument$u002Eformula();
|
||||
public static final MediaType APPLICATION_VND_OASIS_OPENDOCUMENT_GRAPHICS = akka.http.scaladsl.model.MediaTypes.application$divvnd$u002Eoasis$u002Eopendocument$u002Egraphics();
|
||||
public static final MediaType APPLICATION_VND_OASIS_OPENDOCUMENT_IMAGE = akka.http.scaladsl.model.MediaTypes.application$divvnd$u002Eoasis$u002Eopendocument$u002Eimage();
|
||||
public static final MediaType APPLICATION_VND_OASIS_OPENDOCUMENT_PRESENTATION = akka.http.scaladsl.model.MediaTypes.application$divvnd$u002Eoasis$u002Eopendocument$u002Epresentation();
|
||||
public static final MediaType APPLICATION_VND_OASIS_OPENDOCUMENT_SPREADSHEET = akka.http.scaladsl.model.MediaTypes.application$divvnd$u002Eoasis$u002Eopendocument$u002Espreadsheet();
|
||||
public static final MediaType APPLICATION_VND_OASIS_OPENDOCUMENT_TEXT = akka.http.scaladsl.model.MediaTypes.application$divvnd$u002Eoasis$u002Eopendocument$u002Etext();
|
||||
public static final MediaType APPLICATION_VND_OASIS_OPENDOCUMENT_TEXT_MASTER = akka.http.scaladsl.model.MediaTypes.application$divvnd$u002Eoasis$u002Eopendocument$u002Etext$minusmaster();
|
||||
public static final MediaType APPLICATION_VND_OASIS_OPENDOCUMENT_TEXT_WEB = akka.http.scaladsl.model.MediaTypes.application$divvnd$u002Eoasis$u002Eopendocument$u002Etext$minusweb();
|
||||
public static final MediaType APPLICATION_VND_OPENXMLFORMATS_OFFICEDOCUMENT_PRESENTATIONML_PRESENTATION = akka.http.scaladsl.model.MediaTypes.application$divvnd$u002Eopenxmlformats$minusofficedocument$u002Epresentationml$u002Epresentation();
|
||||
public static final MediaType APPLICATION_VND_OPENXMLFORMATS_OFFICEDOCUMENT_PRESENTATIONML_SLIDE = akka.http.scaladsl.model.MediaTypes.application$divvnd$u002Eopenxmlformats$minusofficedocument$u002Epresentationml$u002Eslide();
|
||||
public static final MediaType APPLICATION_VND_OPENXMLFORMATS_OFFICEDOCUMENT_PRESENTATIONML_SLIDESHOW = akka.http.scaladsl.model.MediaTypes.application$divvnd$u002Eopenxmlformats$minusofficedocument$u002Epresentationml$u002Eslideshow();
|
||||
public static final MediaType APPLICATION_VND_OPENXMLFORMATS_OFFICEDOCUMENT_PRESENTATIONML_TEMPLATE = akka.http.scaladsl.model.MediaTypes.application$divvnd$u002Eopenxmlformats$minusofficedocument$u002Epresentationml$u002Etemplate();
|
||||
public static final MediaType APPLICATION_VND_OPENXMLFORMATS_OFFICEDOCUMENT_SPREADSHEETML_SHEET = akka.http.scaladsl.model.MediaTypes.application$divvnd$u002Eopenxmlformats$minusofficedocument$u002Espreadsheetml$u002Esheet();
|
||||
public static final MediaType APPLICATION_VND_OPENXMLFORMATS_OFFICEDOCUMENT_SPREADSHEETML_TEMPLATE = akka.http.scaladsl.model.MediaTypes.application$divvnd$u002Eopenxmlformats$minusofficedocument$u002Espreadsheetml$u002Etemplate();
|
||||
public static final MediaType APPLICATION_VND_OPENXMLFORMATS_OFFICEDOCUMENT_WORDPROCESSINGML_DOCUMENT = akka.http.scaladsl.model.MediaTypes.application$divvnd$u002Eopenxmlformats$minusofficedocument$u002Ewordprocessingml$u002Edocument();
|
||||
public static final MediaType APPLICATION_VND_OPENXMLFORMATS_OFFICEDOCUMENT_WORDPROCESSINGML_TEMPLATE = akka.http.scaladsl.model.MediaTypes.application$divvnd$u002Eopenxmlformats$minusofficedocument$u002Ewordprocessingml$u002Etemplate();
|
||||
public static final MediaType APPLICATION_X_7Z_COMPRESSED = akka.http.scaladsl.model.MediaTypes.application$divx$minus7z$minuscompressed();
|
||||
public static final MediaType APPLICATION_X_ACE_COMPRESSED = akka.http.scaladsl.model.MediaTypes.application$divx$minusace$minuscompressed();
|
||||
public static final MediaType APPLICATION_X_APPLE_DISKIMAGE = akka.http.scaladsl.model.MediaTypes.application$divx$minusapple$minusdiskimage();
|
||||
public static final MediaType APPLICATION_X_ARC_COMPRESSED = akka.http.scaladsl.model.MediaTypes.application$divx$minusarc$minuscompressed();
|
||||
public static final MediaType APPLICATION_X_BZIP = akka.http.scaladsl.model.MediaTypes.application$divx$minusbzip();
|
||||
public static final MediaType APPLICATION_X_BZIP2 = akka.http.scaladsl.model.MediaTypes.application$divx$minusbzip2();
|
||||
public static final MediaType APPLICATION_X_CHROME_EXTENSION = akka.http.scaladsl.model.MediaTypes.application$divx$minuschrome$minusextension();
|
||||
public static final MediaType APPLICATION_X_COMPRESS = akka.http.scaladsl.model.MediaTypes.application$divx$minuscompress();
|
||||
public static final MediaType APPLICATION_X_COMPRESSED = akka.http.scaladsl.model.MediaTypes.application$divx$minuscompressed();
|
||||
public static final MediaType APPLICATION_X_DEBIAN_PACKAGE = akka.http.scaladsl.model.MediaTypes.application$divx$minusdebian$minuspackage();
|
||||
public static final MediaType APPLICATION_X_DVI = akka.http.scaladsl.model.MediaTypes.application$divx$minusdvi();
|
||||
public static final MediaType APPLICATION_X_FONT_TRUETYPE = akka.http.scaladsl.model.MediaTypes.application$divx$minusfont$minustruetype();
|
||||
public static final MediaType APPLICATION_X_FONT_OPENTYPE = akka.http.scaladsl.model.MediaTypes.application$divx$minusfont$minusopentype();
|
||||
public static final MediaType APPLICATION_X_GTAR = akka.http.scaladsl.model.MediaTypes.application$divx$minusgtar();
|
||||
public static final MediaType APPLICATION_X_GZIP = akka.http.scaladsl.model.MediaTypes.application$divx$minusgzip();
|
||||
public static final MediaType APPLICATION_X_LATEX = akka.http.scaladsl.model.MediaTypes.application$divx$minuslatex();
|
||||
public static final MediaType APPLICATION_X_RAR_COMPRESSED = akka.http.scaladsl.model.MediaTypes.application$divx$minusrar$minuscompressed();
|
||||
public static final MediaType APPLICATION_X_REDHAT_PACKAGE_MANAGER = akka.http.scaladsl.model.MediaTypes.application$divx$minusredhat$minuspackage$minusmanager();
|
||||
public static final MediaType APPLICATION_X_SHOCKWAVE_FLASH = akka.http.scaladsl.model.MediaTypes.application$divx$minusshockwave$minusflash();
|
||||
public static final MediaType APPLICATION_X_TAR = akka.http.scaladsl.model.MediaTypes.application$divx$minustar();
|
||||
public static final MediaType APPLICATION_X_TEX = akka.http.scaladsl.model.MediaTypes.application$divx$minustex();
|
||||
public static final MediaType APPLICATION_X_TEXINFO = akka.http.scaladsl.model.MediaTypes.application$divx$minustexinfo();
|
||||
public static final MediaType APPLICATION_X_VRML = akka.http.scaladsl.model.MediaTypes.application$divx$minusvrml();
|
||||
public static final MediaType APPLICATION_X_WWW_FORM_URLENCODED = akka.http.scaladsl.model.MediaTypes.application$divx$minuswww$minusform$minusurlencoded();
|
||||
public static final MediaType APPLICATION_X_X509_CA_CERT = akka.http.scaladsl.model.MediaTypes.application$divx$minusx509$minusca$minuscert();
|
||||
public static final MediaType APPLICATION_X_XPINSTALL = akka.http.scaladsl.model.MediaTypes.application$divx$minusxpinstall();
|
||||
public static final MediaType APPLICATION_XHTML_XML = akka.http.scaladsl.model.MediaTypes.application$divxhtml$plusxml();
|
||||
public static final MediaType APPLICATION_XML_DTD = akka.http.scaladsl.model.MediaTypes.application$divxml$minusdtd();
|
||||
public static final MediaType APPLICATION_XML = akka.http.scaladsl.model.MediaTypes.application$divxml();
|
||||
public static final MediaType APPLICATION_ZIP = akka.http.scaladsl.model.MediaTypes.application$divzip();
|
||||
public static final MediaType AUDIO_AIFF = akka.http.scaladsl.model.MediaTypes.audio$divaiff();
|
||||
public static final MediaType AUDIO_BASIC = akka.http.scaladsl.model.MediaTypes.audio$divbasic();
|
||||
public static final MediaType AUDIO_MIDI = akka.http.scaladsl.model.MediaTypes.audio$divmidi();
|
||||
public static final MediaType AUDIO_MOD = akka.http.scaladsl.model.MediaTypes.audio$divmod();
|
||||
public static final MediaType AUDIO_MPEG = akka.http.scaladsl.model.MediaTypes.audio$divmpeg();
|
||||
public static final MediaType AUDIO_OGG = akka.http.scaladsl.model.MediaTypes.audio$divogg();
|
||||
public static final MediaType AUDIO_VOC = akka.http.scaladsl.model.MediaTypes.audio$divvoc();
|
||||
public static final MediaType AUDIO_VORBIS = akka.http.scaladsl.model.MediaTypes.audio$divvorbis();
|
||||
public static final MediaType AUDIO_VOXWARE = akka.http.scaladsl.model.MediaTypes.audio$divvoxware();
|
||||
public static final MediaType AUDIO_WAV = akka.http.scaladsl.model.MediaTypes.audio$divwav();
|
||||
public static final MediaType AUDIO_X_REALAUDIO = akka.http.scaladsl.model.MediaTypes.audio$divx$minusrealaudio();
|
||||
public static final MediaType AUDIO_X_PSID = akka.http.scaladsl.model.MediaTypes.audio$divx$minuspsid();
|
||||
public static final MediaType AUDIO_XM = akka.http.scaladsl.model.MediaTypes.audio$divxm();
|
||||
public static final MediaType AUDIO_WEBM = akka.http.scaladsl.model.MediaTypes.audio$divwebm();
|
||||
public static final MediaType IMAGE_GIF = akka.http.scaladsl.model.MediaTypes.image$divgif();
|
||||
public static final MediaType IMAGE_JPEG = akka.http.scaladsl.model.MediaTypes.image$divjpeg();
|
||||
public static final MediaType IMAGE_PICT = akka.http.scaladsl.model.MediaTypes.image$divpict();
|
||||
public static final MediaType IMAGE_PNG = akka.http.scaladsl.model.MediaTypes.image$divpng();
|
||||
public static final MediaType IMAGE_SVG_XML = akka.http.scaladsl.model.MediaTypes.image$divsvg$plusxml();
|
||||
public static final MediaType IMAGE_TIFF = akka.http.scaladsl.model.MediaTypes.image$divtiff();
|
||||
public static final MediaType IMAGE_X_ICON = akka.http.scaladsl.model.MediaTypes.image$divx$minusicon();
|
||||
public static final MediaType IMAGE_X_MS_BMP = akka.http.scaladsl.model.MediaTypes.image$divx$minusms$minusbmp();
|
||||
public static final MediaType IMAGE_X_PCX = akka.http.scaladsl.model.MediaTypes.image$divx$minuspcx();
|
||||
public static final MediaType IMAGE_X_PICT = akka.http.scaladsl.model.MediaTypes.image$divx$minuspict();
|
||||
public static final MediaType IMAGE_X_QUICKTIME = akka.http.scaladsl.model.MediaTypes.image$divx$minusquicktime();
|
||||
public static final MediaType IMAGE_X_RGB = akka.http.scaladsl.model.MediaTypes.image$divx$minusrgb();
|
||||
public static final MediaType IMAGE_X_XBITMAP = akka.http.scaladsl.model.MediaTypes.image$divx$minusxbitmap();
|
||||
public static final MediaType IMAGE_X_XPIXMAP = akka.http.scaladsl.model.MediaTypes.image$divx$minusxpixmap();
|
||||
public static final MediaType IMAGE_WEBP = akka.http.scaladsl.model.MediaTypes.image$divwebp();
|
||||
public static final MediaType MESSAGE_HTTP = akka.http.scaladsl.model.MediaTypes.message$divhttp();
|
||||
public static final MediaType MESSAGE_DELIVERY_STATUS = akka.http.scaladsl.model.MediaTypes.message$divdelivery$minusstatus();
|
||||
public static final MediaType MESSAGE_RFC822 = akka.http.scaladsl.model.MediaTypes.message$divrfc822();
|
||||
public static final MediaType MULTIPART_MIXED = akka.http.scaladsl.model.MediaTypes.multipart$divmixed();
|
||||
public static final MediaType MULTIPART_ALTERNATIVE = akka.http.scaladsl.model.MediaTypes.multipart$divalternative();
|
||||
public static final MediaType MULTIPART_RELATED = akka.http.scaladsl.model.MediaTypes.multipart$divrelated();
|
||||
public static final MediaType MULTIPART_FORM_DATA = akka.http.scaladsl.model.MediaTypes.multipart$divform$minusdata();
|
||||
public static final MediaType MULTIPART_SIGNED = akka.http.scaladsl.model.MediaTypes.multipart$divsigned();
|
||||
public static final MediaType MULTIPART_ENCRYPTED = akka.http.scaladsl.model.MediaTypes.multipart$divencrypted();
|
||||
public static final MediaType MULTIPART_BYTERANGES = akka.http.scaladsl.model.MediaTypes.multipart$divbyteranges();
|
||||
public static final MediaType TEXT_ASP = akka.http.scaladsl.model.MediaTypes.text$divasp();
|
||||
public static final MediaType TEXT_CACHE_MANIFEST = akka.http.scaladsl.model.MediaTypes.text$divcache$minusmanifest();
|
||||
public static final MediaType TEXT_CALENDAR = akka.http.scaladsl.model.MediaTypes.text$divcalendar();
|
||||
public static final MediaType TEXT_CSS = akka.http.scaladsl.model.MediaTypes.text$divcss();
|
||||
public static final MediaType TEXT_CSV = akka.http.scaladsl.model.MediaTypes.text$divcsv();
|
||||
public static final MediaType TEXT_HTML = akka.http.scaladsl.model.MediaTypes.text$divhtml();
|
||||
public static final MediaType TEXT_MCF = akka.http.scaladsl.model.MediaTypes.text$divmcf();
|
||||
public static final MediaType TEXT_PLAIN = akka.http.scaladsl.model.MediaTypes.text$divplain();
|
||||
public static final MediaType TEXT_RICHTEXT = akka.http.scaladsl.model.MediaTypes.text$divrichtext();
|
||||
public static final MediaType TEXT_TAB_SEPARATED_VALUES = akka.http.scaladsl.model.MediaTypes.text$divtab$minusseparated$minusvalues();
|
||||
public static final MediaType TEXT_URI_LIST = akka.http.scaladsl.model.MediaTypes.text$divuri$minuslist();
|
||||
public static final MediaType TEXT_VND_WAP_WML = akka.http.scaladsl.model.MediaTypes.text$divvnd$u002Ewap$u002Ewml();
|
||||
public static final MediaType TEXT_VND_WAP_WMLSCRIPT = akka.http.scaladsl.model.MediaTypes.text$divvnd$u002Ewap$u002Ewmlscript();
|
||||
public static final MediaType TEXT_X_ASM = akka.http.scaladsl.model.MediaTypes.text$divx$minusasm();
|
||||
public static final MediaType TEXT_X_C = akka.http.scaladsl.model.MediaTypes.text$divx$minusc();
|
||||
public static final MediaType TEXT_X_COMPONENT = akka.http.scaladsl.model.MediaTypes.text$divx$minuscomponent();
|
||||
public static final MediaType TEXT_X_H = akka.http.scaladsl.model.MediaTypes.text$divx$minush();
|
||||
public static final MediaType TEXT_X_JAVA_SOURCE = akka.http.scaladsl.model.MediaTypes.text$divx$minusjava$minussource();
|
||||
public static final MediaType TEXT_X_PASCAL = akka.http.scaladsl.model.MediaTypes.text$divx$minuspascal();
|
||||
public static final MediaType TEXT_X_SCRIPT = akka.http.scaladsl.model.MediaTypes.text$divx$minusscript();
|
||||
public static final MediaType TEXT_X_SCRIPTCSH = akka.http.scaladsl.model.MediaTypes.text$divx$minusscriptcsh();
|
||||
public static final MediaType TEXT_X_SCRIPTELISP = akka.http.scaladsl.model.MediaTypes.text$divx$minusscriptelisp();
|
||||
public static final MediaType TEXT_X_SCRIPTKSH = akka.http.scaladsl.model.MediaTypes.text$divx$minusscriptksh();
|
||||
public static final MediaType TEXT_X_SCRIPTLISP = akka.http.scaladsl.model.MediaTypes.text$divx$minusscriptlisp();
|
||||
public static final MediaType TEXT_X_SCRIPTPERL = akka.http.scaladsl.model.MediaTypes.text$divx$minusscriptperl();
|
||||
public static final MediaType TEXT_X_SCRIPTPERL_MODULE = akka.http.scaladsl.model.MediaTypes.text$divx$minusscriptperl$minusmodule();
|
||||
public static final MediaType TEXT_X_SCRIPTPHYTON = akka.http.scaladsl.model.MediaTypes.text$divx$minusscriptphyton();
|
||||
public static final MediaType TEXT_X_SCRIPTREXX = akka.http.scaladsl.model.MediaTypes.text$divx$minusscriptrexx();
|
||||
public static final MediaType TEXT_X_SCRIPTSCHEME = akka.http.scaladsl.model.MediaTypes.text$divx$minusscriptscheme();
|
||||
public static final MediaType TEXT_X_SCRIPTSH = akka.http.scaladsl.model.MediaTypes.text$divx$minusscriptsh();
|
||||
public static final MediaType TEXT_X_SCRIPTTCL = akka.http.scaladsl.model.MediaTypes.text$divx$minusscripttcl();
|
||||
public static final MediaType TEXT_X_SCRIPTTCSH = akka.http.scaladsl.model.MediaTypes.text$divx$minusscripttcsh();
|
||||
public static final MediaType TEXT_X_SCRIPTZSH = akka.http.scaladsl.model.MediaTypes.text$divx$minusscriptzsh();
|
||||
public static final MediaType TEXT_X_SERVER_PARSED_HTML = akka.http.scaladsl.model.MediaTypes.text$divx$minusserver$minusparsed$minushtml();
|
||||
public static final MediaType TEXT_X_SETEXT = akka.http.scaladsl.model.MediaTypes.text$divx$minussetext();
|
||||
public static final MediaType TEXT_X_SGML = akka.http.scaladsl.model.MediaTypes.text$divx$minussgml();
|
||||
public static final MediaType TEXT_X_SPEECH = akka.http.scaladsl.model.MediaTypes.text$divx$minusspeech();
|
||||
public static final MediaType TEXT_X_UUENCODE = akka.http.scaladsl.model.MediaTypes.text$divx$minusuuencode();
|
||||
public static final MediaType TEXT_X_VCALENDAR = akka.http.scaladsl.model.MediaTypes.text$divx$minusvcalendar();
|
||||
public static final MediaType TEXT_X_VCARD = akka.http.scaladsl.model.MediaTypes.text$divx$minusvcard();
|
||||
public static final MediaType TEXT_XML = akka.http.scaladsl.model.MediaTypes.text$divxml();
|
||||
public static final MediaType VIDEO_AVS_VIDEO = akka.http.scaladsl.model.MediaTypes.video$divavs$minusvideo();
|
||||
public static final MediaType VIDEO_DIVX = akka.http.scaladsl.model.MediaTypes.video$divdivx();
|
||||
public static final MediaType VIDEO_GL = akka.http.scaladsl.model.MediaTypes.video$divgl();
|
||||
public static final MediaType VIDEO_MP4 = akka.http.scaladsl.model.MediaTypes.video$divmp4();
|
||||
public static final MediaType VIDEO_MPEG = akka.http.scaladsl.model.MediaTypes.video$divmpeg();
|
||||
public static final MediaType VIDEO_OGG = akka.http.scaladsl.model.MediaTypes.video$divogg();
|
||||
public static final MediaType VIDEO_QUICKTIME = akka.http.scaladsl.model.MediaTypes.video$divquicktime();
|
||||
public static final MediaType VIDEO_X_DV = akka.http.scaladsl.model.MediaTypes.video$divx$minusdv();
|
||||
public static final MediaType VIDEO_X_FLV = akka.http.scaladsl.model.MediaTypes.video$divx$minusflv();
|
||||
public static final MediaType VIDEO_X_MOTION_JPEG = akka.http.scaladsl.model.MediaTypes.video$divx$minusmotion$minusjpeg();
|
||||
public static final MediaType VIDEO_X_MS_ASF = akka.http.scaladsl.model.MediaTypes.video$divx$minusms$minusasf();
|
||||
public static final MediaType VIDEO_X_MSVIDEO = akka.http.scaladsl.model.MediaTypes.video$divx$minusmsvideo();
|
||||
public static final MediaType VIDEO_X_SGI_MOVIE = akka.http.scaladsl.model.MediaTypes.video$divx$minussgi$minusmovie();
|
||||
public static final MediaType VIDEO_WEBM = akka.http.scaladsl.model.MediaTypes.video$divwebm();
|
||||
public static final MediaType.WithOpenCharset APPLICATION_ATOM_XML = akka.http.scaladsl.model.MediaTypes.application$divatom$plusxml();
|
||||
public static final MediaType.WithOpenCharset APPLICATION_BASE64 = akka.http.scaladsl.model.MediaTypes.application$divbase64();
|
||||
public static final MediaType.Binary APPLICATION_EXCEL = akka.http.scaladsl.model.MediaTypes.application$divexcel();
|
||||
public static final MediaType.Binary APPLICATION_FONT_WOFF = akka.http.scaladsl.model.MediaTypes.application$divfont$minuswoff();
|
||||
public static final MediaType.Binary APPLICATION_GNUTAR = akka.http.scaladsl.model.MediaTypes.application$divgnutar();
|
||||
public static final MediaType.Binary APPLICATION_JAVA_ARCHIVE = akka.http.scaladsl.model.MediaTypes.application$divjava$minusarchive();
|
||||
public static final MediaType.WithOpenCharset APPLICATION_JAVASCRIPT = akka.http.scaladsl.model.MediaTypes.application$divjavascript();
|
||||
public static final MediaType.WithFixedCharset APPLICATION_JSON = akka.http.scaladsl.model.MediaTypes.application$divjson();
|
||||
public static final MediaType.WithFixedCharset APPLICATION_JSON_PATCH_JSON = akka.http.scaladsl.model.MediaTypes.application$divjson$minuspatch$plusjson();
|
||||
public static final MediaType.Binary APPLICATION_LHA = akka.http.scaladsl.model.MediaTypes.application$divlha();
|
||||
public static final MediaType.Binary APPLICATION_LZX = akka.http.scaladsl.model.MediaTypes.application$divlzx();
|
||||
public static final MediaType.Binary APPLICATION_MSPOWERPOINT = akka.http.scaladsl.model.MediaTypes.application$divmspowerpoint();
|
||||
public static final MediaType.Binary APPLICATION_MSWORD = akka.http.scaladsl.model.MediaTypes.application$divmsword();
|
||||
public static final MediaType.Binary APPLICATION_OCTET_STREAM = akka.http.scaladsl.model.MediaTypes.application$divoctet$minusstream();
|
||||
public static final MediaType.Binary APPLICATION_PDF = akka.http.scaladsl.model.MediaTypes.application$divpdf();
|
||||
public static final MediaType.Binary APPLICATION_POSTSCRIPT = akka.http.scaladsl.model.MediaTypes.application$divpostscript();
|
||||
public static final MediaType.WithOpenCharset APPLICATION_RSS_XML = akka.http.scaladsl.model.MediaTypes.application$divrss$plusxml();
|
||||
public static final MediaType.WithOpenCharset APPLICATION_SOAP_XML = akka.http.scaladsl.model.MediaTypes.application$divsoap$plusxml();
|
||||
public static final MediaType.WithFixedCharset APPLICATION_VND_API_JSON = akka.http.scaladsl.model.MediaTypes.application$divvnd$u002Eapi$plusjson();
|
||||
public static final MediaType.WithOpenCharset APPLICATION_VND_GOOGLE_EARTH_KML_XML = akka.http.scaladsl.model.MediaTypes.application$divvnd$u002Egoogle$minusearth$u002Ekml$plusxml();
|
||||
public static final MediaType.Binary APPLICATION_VND_GOOGLE_EARTH_KMZ = akka.http.scaladsl.model.MediaTypes.application$divvnd$u002Egoogle$minusearth$u002Ekmz();
|
||||
public static final MediaType.Binary APPLICATION_VND_MS_FONTOBJECT = akka.http.scaladsl.model.MediaTypes.application$divvnd$u002Ems$minusfontobject();
|
||||
public static final MediaType.Binary APPLICATION_VND_OASIS_OPENDOCUMENT_CHART = akka.http.scaladsl.model.MediaTypes.application$divvnd$u002Eoasis$u002Eopendocument$u002Echart();
|
||||
public static final MediaType.Binary APPLICATION_VND_OASIS_OPENDOCUMENT_DATABASE = akka.http.scaladsl.model.MediaTypes.application$divvnd$u002Eoasis$u002Eopendocument$u002Edatabase();
|
||||
public static final MediaType.Binary APPLICATION_VND_OASIS_OPENDOCUMENT_FORMULA = akka.http.scaladsl.model.MediaTypes.application$divvnd$u002Eoasis$u002Eopendocument$u002Eformula();
|
||||
public static final MediaType.Binary APPLICATION_VND_OASIS_OPENDOCUMENT_GRAPHICS = akka.http.scaladsl.model.MediaTypes.application$divvnd$u002Eoasis$u002Eopendocument$u002Egraphics();
|
||||
public static final MediaType.Binary APPLICATION_VND_OASIS_OPENDOCUMENT_IMAGE = akka.http.scaladsl.model.MediaTypes.application$divvnd$u002Eoasis$u002Eopendocument$u002Eimage();
|
||||
public static final MediaType.Binary APPLICATION_VND_OASIS_OPENDOCUMENT_PRESENTATION = akka.http.scaladsl.model.MediaTypes.application$divvnd$u002Eoasis$u002Eopendocument$u002Epresentation();
|
||||
public static final MediaType.Binary APPLICATION_VND_OASIS_OPENDOCUMENT_SPREADSHEET = akka.http.scaladsl.model.MediaTypes.application$divvnd$u002Eoasis$u002Eopendocument$u002Espreadsheet();
|
||||
public static final MediaType.Binary APPLICATION_VND_OASIS_OPENDOCUMENT_TEXT = akka.http.scaladsl.model.MediaTypes.application$divvnd$u002Eoasis$u002Eopendocument$u002Etext();
|
||||
public static final MediaType.Binary APPLICATION_VND_OASIS_OPENDOCUMENT_TEXT_MASTER = akka.http.scaladsl.model.MediaTypes.application$divvnd$u002Eoasis$u002Eopendocument$u002Etext$minusmaster();
|
||||
public static final MediaType.Binary APPLICATION_VND_OASIS_OPENDOCUMENT_TEXT_WEB = akka.http.scaladsl.model.MediaTypes.application$divvnd$u002Eoasis$u002Eopendocument$u002Etext$minusweb();
|
||||
public static final MediaType.Binary APPLICATION_VND_OPENXMLFORMATS_OFFICEDOCUMENT_PRESENTATIONML_PRESENTATION = akka.http.scaladsl.model.MediaTypes.application$divvnd$u002Eopenxmlformats$minusofficedocument$u002Epresentationml$u002Epresentation();
|
||||
public static final MediaType.Binary APPLICATION_VND_OPENXMLFORMATS_OFFICEDOCUMENT_PRESENTATIONML_SLIDE = akka.http.scaladsl.model.MediaTypes.application$divvnd$u002Eopenxmlformats$minusofficedocument$u002Epresentationml$u002Eslide();
|
||||
public static final MediaType.Binary APPLICATION_VND_OPENXMLFORMATS_OFFICEDOCUMENT_PRESENTATIONML_SLIDESHOW = akka.http.scaladsl.model.MediaTypes.application$divvnd$u002Eopenxmlformats$minusofficedocument$u002Epresentationml$u002Eslideshow();
|
||||
public static final MediaType.Binary APPLICATION_VND_OPENXMLFORMATS_OFFICEDOCUMENT_PRESENTATIONML_TEMPLATE = akka.http.scaladsl.model.MediaTypes.application$divvnd$u002Eopenxmlformats$minusofficedocument$u002Epresentationml$u002Etemplate();
|
||||
public static final MediaType.Binary APPLICATION_VND_OPENXMLFORMATS_OFFICEDOCUMENT_SPREADSHEETML_SHEET = akka.http.scaladsl.model.MediaTypes.application$divvnd$u002Eopenxmlformats$minusofficedocument$u002Espreadsheetml$u002Esheet();
|
||||
public static final MediaType.Binary APPLICATION_VND_OPENXMLFORMATS_OFFICEDOCUMENT_SPREADSHEETML_TEMPLATE = akka.http.scaladsl.model.MediaTypes.application$divvnd$u002Eopenxmlformats$minusofficedocument$u002Espreadsheetml$u002Etemplate();
|
||||
public static final MediaType.Binary APPLICATION_VND_OPENXMLFORMATS_OFFICEDOCUMENT_WORDPROCESSINGML_DOCUMENT = akka.http.scaladsl.model.MediaTypes.application$divvnd$u002Eopenxmlformats$minusofficedocument$u002Ewordprocessingml$u002Edocument();
|
||||
public static final MediaType.Binary APPLICATION_VND_OPENXMLFORMATS_OFFICEDOCUMENT_WORDPROCESSINGML_TEMPLATE = akka.http.scaladsl.model.MediaTypes.application$divvnd$u002Eopenxmlformats$minusofficedocument$u002Ewordprocessingml$u002Etemplate();
|
||||
public static final MediaType.Binary APPLICATION_X_7Z_COMPRESSED = akka.http.scaladsl.model.MediaTypes.application$divx$minus7z$minuscompressed();
|
||||
public static final MediaType.Binary APPLICATION_X_ACE_COMPRESSED = akka.http.scaladsl.model.MediaTypes.application$divx$minusace$minuscompressed();
|
||||
public static final MediaType.Binary APPLICATION_X_APPLE_DISKIMAGE = akka.http.scaladsl.model.MediaTypes.application$divx$minusapple$minusdiskimage();
|
||||
public static final MediaType.Binary APPLICATION_X_ARC_COMPRESSED = akka.http.scaladsl.model.MediaTypes.application$divx$minusarc$minuscompressed();
|
||||
public static final MediaType.Binary APPLICATION_X_BZIP = akka.http.scaladsl.model.MediaTypes.application$divx$minusbzip();
|
||||
public static final MediaType.Binary APPLICATION_X_BZIP2 = akka.http.scaladsl.model.MediaTypes.application$divx$minusbzip2();
|
||||
public static final MediaType.Binary APPLICATION_X_CHROME_EXTENSION = akka.http.scaladsl.model.MediaTypes.application$divx$minuschrome$minusextension();
|
||||
public static final MediaType.Binary APPLICATION_X_COMPRESS = akka.http.scaladsl.model.MediaTypes.application$divx$minuscompress();
|
||||
public static final MediaType.Binary APPLICATION_X_COMPRESSED = akka.http.scaladsl.model.MediaTypes.application$divx$minuscompressed();
|
||||
public static final MediaType.Binary APPLICATION_X_DEBIAN_PACKAGE = akka.http.scaladsl.model.MediaTypes.application$divx$minusdebian$minuspackage();
|
||||
public static final MediaType.Binary APPLICATION_X_DVI = akka.http.scaladsl.model.MediaTypes.application$divx$minusdvi();
|
||||
public static final MediaType.Binary APPLICATION_X_FONT_TRUETYPE = akka.http.scaladsl.model.MediaTypes.application$divx$minusfont$minustruetype();
|
||||
public static final MediaType.Binary APPLICATION_X_FONT_OPENTYPE = akka.http.scaladsl.model.MediaTypes.application$divx$minusfont$minusopentype();
|
||||
public static final MediaType.Binary APPLICATION_X_GTAR = akka.http.scaladsl.model.MediaTypes.application$divx$minusgtar();
|
||||
public static final MediaType.Binary APPLICATION_X_GZIP = akka.http.scaladsl.model.MediaTypes.application$divx$minusgzip();
|
||||
public static final MediaType.WithOpenCharset APPLICATION_X_LATEX = akka.http.scaladsl.model.MediaTypes.application$divx$minuslatex();
|
||||
public static final MediaType.Binary APPLICATION_X_RAR_COMPRESSED = akka.http.scaladsl.model.MediaTypes.application$divx$minusrar$minuscompressed();
|
||||
public static final MediaType.Binary APPLICATION_X_REDHAT_PACKAGE_MANAGER = akka.http.scaladsl.model.MediaTypes.application$divx$minusredhat$minuspackage$minusmanager();
|
||||
public static final MediaType.Binary APPLICATION_X_SHOCKWAVE_FLASH = akka.http.scaladsl.model.MediaTypes.application$divx$minusshockwave$minusflash();
|
||||
public static final MediaType.Binary APPLICATION_X_TAR = akka.http.scaladsl.model.MediaTypes.application$divx$minustar();
|
||||
public static final MediaType.Binary APPLICATION_X_TEX = akka.http.scaladsl.model.MediaTypes.application$divx$minustex();
|
||||
public static final MediaType.Binary APPLICATION_X_TEXINFO = akka.http.scaladsl.model.MediaTypes.application$divx$minustexinfo();
|
||||
public static final MediaType.WithOpenCharset APPLICATION_X_VRML = akka.http.scaladsl.model.MediaTypes.application$divx$minusvrml();
|
||||
public static final MediaType.WithOpenCharset APPLICATION_X_WWW_FORM_URLENCODED = akka.http.scaladsl.model.MediaTypes.application$divx$minuswww$minusform$minusurlencoded();
|
||||
public static final MediaType.Binary APPLICATION_X_X509_CA_CERT = akka.http.scaladsl.model.MediaTypes.application$divx$minusx509$minusca$minuscert();
|
||||
public static final MediaType.Binary APPLICATION_X_XPINSTALL = akka.http.scaladsl.model.MediaTypes.application$divx$minusxpinstall();
|
||||
public static final MediaType.WithOpenCharset APPLICATION_XHTML_XML = akka.http.scaladsl.model.MediaTypes.application$divxhtml$plusxml();
|
||||
public static final MediaType.WithOpenCharset APPLICATION_XML_DTD = akka.http.scaladsl.model.MediaTypes.application$divxml$minusdtd();
|
||||
public static final MediaType.WithOpenCharset APPLICATION_XML = akka.http.scaladsl.model.MediaTypes.application$divxml();
|
||||
public static final MediaType.Binary APPLICATION_ZIP = akka.http.scaladsl.model.MediaTypes.application$divzip();
|
||||
|
||||
public static final MediaType.Binary AUDIO_AIFF = akka.http.scaladsl.model.MediaTypes.audio$divaiff();
|
||||
public static final MediaType.Binary AUDIO_BASIC = akka.http.scaladsl.model.MediaTypes.audio$divbasic();
|
||||
public static final MediaType.Binary AUDIO_MIDI = akka.http.scaladsl.model.MediaTypes.audio$divmidi();
|
||||
public static final MediaType.Binary AUDIO_MOD = akka.http.scaladsl.model.MediaTypes.audio$divmod();
|
||||
public static final MediaType.Binary AUDIO_MPEG = akka.http.scaladsl.model.MediaTypes.audio$divmpeg();
|
||||
public static final MediaType.Binary AUDIO_OGG = akka.http.scaladsl.model.MediaTypes.audio$divogg();
|
||||
public static final MediaType.Binary AUDIO_VOC = akka.http.scaladsl.model.MediaTypes.audio$divvoc();
|
||||
public static final MediaType.Binary AUDIO_VORBIS = akka.http.scaladsl.model.MediaTypes.audio$divvorbis();
|
||||
public static final MediaType.Binary AUDIO_VOXWARE = akka.http.scaladsl.model.MediaTypes.audio$divvoxware();
|
||||
public static final MediaType.Binary AUDIO_WAV = akka.http.scaladsl.model.MediaTypes.audio$divwav();
|
||||
public static final MediaType.Binary AUDIO_X_REALAUDIO = akka.http.scaladsl.model.MediaTypes.audio$divx$minusrealaudio();
|
||||
public static final MediaType.Binary AUDIO_X_PSID = akka.http.scaladsl.model.MediaTypes.audio$divx$minuspsid();
|
||||
public static final MediaType.Binary AUDIO_XM = akka.http.scaladsl.model.MediaTypes.audio$divxm();
|
||||
public static final MediaType.Binary AUDIO_WEBM = akka.http.scaladsl.model.MediaTypes.audio$divwebm();
|
||||
|
||||
public static final MediaType.Binary IMAGE_GIF = akka.http.scaladsl.model.MediaTypes.image$divgif();
|
||||
public static final MediaType.Binary IMAGE_JPEG = akka.http.scaladsl.model.MediaTypes.image$divjpeg();
|
||||
public static final MediaType.Binary IMAGE_PICT = akka.http.scaladsl.model.MediaTypes.image$divpict();
|
||||
public static final MediaType.Binary IMAGE_PNG = akka.http.scaladsl.model.MediaTypes.image$divpng();
|
||||
public static final MediaType.Binary IMAGE_SVG_XML = akka.http.scaladsl.model.MediaTypes.image$divsvg$plusxml();
|
||||
public static final MediaType.Binary IMAGE_TIFF = akka.http.scaladsl.model.MediaTypes.image$divtiff();
|
||||
public static final MediaType.Binary IMAGE_X_ICON = akka.http.scaladsl.model.MediaTypes.image$divx$minusicon();
|
||||
public static final MediaType.Binary IMAGE_X_MS_BMP = akka.http.scaladsl.model.MediaTypes.image$divx$minusms$minusbmp();
|
||||
public static final MediaType.Binary IMAGE_X_PCX = akka.http.scaladsl.model.MediaTypes.image$divx$minuspcx();
|
||||
public static final MediaType.Binary IMAGE_X_PICT = akka.http.scaladsl.model.MediaTypes.image$divx$minuspict();
|
||||
public static final MediaType.Binary IMAGE_X_QUICKTIME = akka.http.scaladsl.model.MediaTypes.image$divx$minusquicktime();
|
||||
public static final MediaType.Binary IMAGE_X_RGB = akka.http.scaladsl.model.MediaTypes.image$divx$minusrgb();
|
||||
public static final MediaType.Binary IMAGE_X_XBITMAP = akka.http.scaladsl.model.MediaTypes.image$divx$minusxbitmap();
|
||||
public static final MediaType.Binary IMAGE_X_XPIXMAP = akka.http.scaladsl.model.MediaTypes.image$divx$minusxpixmap();
|
||||
public static final MediaType.Binary IMAGE_WEBP = akka.http.scaladsl.model.MediaTypes.image$divwebp();
|
||||
|
||||
public static final MediaType.Binary MESSAGE_HTTP = akka.http.scaladsl.model.MediaTypes.message$divhttp();
|
||||
public static final MediaType.Binary MESSAGE_DELIVERY_STATUS = akka.http.scaladsl.model.MediaTypes.message$divdelivery$minusstatus();
|
||||
public static final MediaType.Binary MESSAGE_RFC822 = akka.http.scaladsl.model.MediaTypes.message$divrfc822();
|
||||
|
||||
public static final MediaType.WithOpenCharset MULTIPART_MIXED = akka.http.scaladsl.model.MediaTypes.multipart$divmixed();
|
||||
public static final MediaType.WithOpenCharset MULTIPART_ALTERNATIVE = akka.http.scaladsl.model.MediaTypes.multipart$divalternative();
|
||||
public static final MediaType.WithOpenCharset MULTIPART_RELATED = akka.http.scaladsl.model.MediaTypes.multipart$divrelated();
|
||||
public static final MediaType.WithOpenCharset MULTIPART_FORM_DATA = akka.http.scaladsl.model.MediaTypes.multipart$divform$minusdata();
|
||||
public static final MediaType.WithOpenCharset MULTIPART_SIGNED = akka.http.scaladsl.model.MediaTypes.multipart$divsigned();
|
||||
public static final MediaType.WithOpenCharset MULTIPART_ENCRYPTED = akka.http.scaladsl.model.MediaTypes.multipart$divencrypted();
|
||||
public static final MediaType.WithOpenCharset MULTIPART_BYTERANGES = akka.http.scaladsl.model.MediaTypes.multipart$divbyteranges();
|
||||
|
||||
public static final MediaType.WithOpenCharset TEXT_ASP = akka.http.scaladsl.model.MediaTypes.text$divasp();
|
||||
public static final MediaType.WithOpenCharset TEXT_CACHE_MANIFEST = akka.http.scaladsl.model.MediaTypes.text$divcache$minusmanifest();
|
||||
public static final MediaType.WithOpenCharset TEXT_CALENDAR = akka.http.scaladsl.model.MediaTypes.text$divcalendar();
|
||||
public static final MediaType.WithOpenCharset TEXT_CSS = akka.http.scaladsl.model.MediaTypes.text$divcss();
|
||||
public static final MediaType.WithOpenCharset TEXT_CSV = akka.http.scaladsl.model.MediaTypes.text$divcsv();
|
||||
public static final MediaType.WithOpenCharset TEXT_HTML = akka.http.scaladsl.model.MediaTypes.text$divhtml();
|
||||
public static final MediaType.WithOpenCharset TEXT_MCF = akka.http.scaladsl.model.MediaTypes.text$divmcf();
|
||||
public static final MediaType.WithOpenCharset TEXT_PLAIN = akka.http.scaladsl.model.MediaTypes.text$divplain();
|
||||
public static final MediaType.WithOpenCharset TEXT_RICHTEXT = akka.http.scaladsl.model.MediaTypes.text$divrichtext();
|
||||
public static final MediaType.WithOpenCharset TEXT_TAB_SEPARATED_VALUES = akka.http.scaladsl.model.MediaTypes.text$divtab$minusseparated$minusvalues();
|
||||
public static final MediaType.WithOpenCharset TEXT_URI_LIST = akka.http.scaladsl.model.MediaTypes.text$divuri$minuslist();
|
||||
public static final MediaType.WithOpenCharset TEXT_VND_WAP_WML = akka.http.scaladsl.model.MediaTypes.text$divvnd$u002Ewap$u002Ewml();
|
||||
public static final MediaType.WithOpenCharset TEXT_VND_WAP_WMLSCRIPT = akka.http.scaladsl.model.MediaTypes.text$divvnd$u002Ewap$u002Ewmlscript();
|
||||
public static final MediaType.WithOpenCharset TEXT_X_ASM = akka.http.scaladsl.model.MediaTypes.text$divx$minusasm();
|
||||
public static final MediaType.WithOpenCharset TEXT_X_C = akka.http.scaladsl.model.MediaTypes.text$divx$minusc();
|
||||
public static final MediaType.WithOpenCharset TEXT_X_COMPONENT = akka.http.scaladsl.model.MediaTypes.text$divx$minuscomponent();
|
||||
public static final MediaType.WithOpenCharset TEXT_X_H = akka.http.scaladsl.model.MediaTypes.text$divx$minush();
|
||||
public static final MediaType.WithOpenCharset TEXT_X_JAVA_SOURCE = akka.http.scaladsl.model.MediaTypes.text$divx$minusjava$minussource();
|
||||
public static final MediaType.WithOpenCharset TEXT_X_PASCAL = akka.http.scaladsl.model.MediaTypes.text$divx$minuspascal();
|
||||
public static final MediaType.WithOpenCharset TEXT_X_SCRIPT = akka.http.scaladsl.model.MediaTypes.text$divx$minusscript();
|
||||
public static final MediaType.WithOpenCharset TEXT_X_SCRIPTCSH = akka.http.scaladsl.model.MediaTypes.text$divx$minusscriptcsh();
|
||||
public static final MediaType.WithOpenCharset TEXT_X_SCRIPTELISP = akka.http.scaladsl.model.MediaTypes.text$divx$minusscriptelisp();
|
||||
public static final MediaType.WithOpenCharset TEXT_X_SCRIPTKSH = akka.http.scaladsl.model.MediaTypes.text$divx$minusscriptksh();
|
||||
public static final MediaType.WithOpenCharset TEXT_X_SCRIPTLISP = akka.http.scaladsl.model.MediaTypes.text$divx$minusscriptlisp();
|
||||
public static final MediaType.WithOpenCharset TEXT_X_SCRIPTPERL = akka.http.scaladsl.model.MediaTypes.text$divx$minusscriptperl();
|
||||
public static final MediaType.WithOpenCharset TEXT_X_SCRIPTPERL_MODULE = akka.http.scaladsl.model.MediaTypes.text$divx$minusscriptperl$minusmodule();
|
||||
public static final MediaType.WithOpenCharset TEXT_X_SCRIPTPHYTON = akka.http.scaladsl.model.MediaTypes.text$divx$minusscriptphyton();
|
||||
public static final MediaType.WithOpenCharset TEXT_X_SCRIPTREXX = akka.http.scaladsl.model.MediaTypes.text$divx$minusscriptrexx();
|
||||
public static final MediaType.WithOpenCharset TEXT_X_SCRIPTSCHEME = akka.http.scaladsl.model.MediaTypes.text$divx$minusscriptscheme();
|
||||
public static final MediaType.WithOpenCharset TEXT_X_SCRIPTSH = akka.http.scaladsl.model.MediaTypes.text$divx$minusscriptsh();
|
||||
public static final MediaType.WithOpenCharset TEXT_X_SCRIPTTCL = akka.http.scaladsl.model.MediaTypes.text$divx$minusscripttcl();
|
||||
public static final MediaType.WithOpenCharset TEXT_X_SCRIPTTCSH = akka.http.scaladsl.model.MediaTypes.text$divx$minusscripttcsh();
|
||||
public static final MediaType.WithOpenCharset TEXT_X_SCRIPTZSH = akka.http.scaladsl.model.MediaTypes.text$divx$minusscriptzsh();
|
||||
public static final MediaType.WithOpenCharset TEXT_X_SERVER_PARSED_HTML = akka.http.scaladsl.model.MediaTypes.text$divx$minusserver$minusparsed$minushtml();
|
||||
public static final MediaType.WithOpenCharset TEXT_X_SETEXT = akka.http.scaladsl.model.MediaTypes.text$divx$minussetext();
|
||||
public static final MediaType.WithOpenCharset TEXT_X_SGML = akka.http.scaladsl.model.MediaTypes.text$divx$minussgml();
|
||||
public static final MediaType.WithOpenCharset TEXT_X_SPEECH = akka.http.scaladsl.model.MediaTypes.text$divx$minusspeech();
|
||||
public static final MediaType.WithOpenCharset TEXT_X_UUENCODE = akka.http.scaladsl.model.MediaTypes.text$divx$minusuuencode();
|
||||
public static final MediaType.WithOpenCharset TEXT_X_VCALENDAR = akka.http.scaladsl.model.MediaTypes.text$divx$minusvcalendar();
|
||||
public static final MediaType.WithOpenCharset TEXT_X_VCARD = akka.http.scaladsl.model.MediaTypes.text$divx$minusvcard();
|
||||
public static final MediaType.WithOpenCharset TEXT_XML = akka.http.scaladsl.model.MediaTypes.text$divxml();
|
||||
|
||||
public static final MediaType.Binary VIDEO_AVS_VIDEO = akka.http.scaladsl.model.MediaTypes.video$divavs$minusvideo();
|
||||
public static final MediaType.Binary VIDEO_DIVX = akka.http.scaladsl.model.MediaTypes.video$divdivx();
|
||||
public static final MediaType.Binary VIDEO_GL = akka.http.scaladsl.model.MediaTypes.video$divgl();
|
||||
public static final MediaType.Binary VIDEO_MP4 = akka.http.scaladsl.model.MediaTypes.video$divmp4();
|
||||
public static final MediaType.Binary VIDEO_MPEG = akka.http.scaladsl.model.MediaTypes.video$divmpeg();
|
||||
public static final MediaType.Binary VIDEO_OGG = akka.http.scaladsl.model.MediaTypes.video$divogg();
|
||||
public static final MediaType.Binary VIDEO_QUICKTIME = akka.http.scaladsl.model.MediaTypes.video$divquicktime();
|
||||
public static final MediaType.Binary VIDEO_X_DV = akka.http.scaladsl.model.MediaTypes.video$divx$minusdv();
|
||||
public static final MediaType.Binary VIDEO_X_FLV = akka.http.scaladsl.model.MediaTypes.video$divx$minusflv();
|
||||
public static final MediaType.Binary VIDEO_X_MOTION_JPEG = akka.http.scaladsl.model.MediaTypes.video$divx$minusmotion$minusjpeg();
|
||||
public static final MediaType.Binary VIDEO_X_MS_ASF = akka.http.scaladsl.model.MediaTypes.video$divx$minusms$minusasf();
|
||||
public static final MediaType.Binary VIDEO_X_MSVIDEO = akka.http.scaladsl.model.MediaTypes.video$divx$minusmsvideo();
|
||||
public static final MediaType.Binary VIDEO_X_SGI_MOVIE = akka.http.scaladsl.model.MediaTypes.video$divx$minussgi$minusmovie();
|
||||
public static final MediaType.Binary VIDEO_WEBM = akka.http.scaladsl.model.MediaTypes.video$divwebm();
|
||||
|
||||
/**
|
||||
* Creates a custom media type.
|
||||
*/
|
||||
public static MediaType custom(
|
||||
String mainType,
|
||||
String subType,
|
||||
boolean compressible,
|
||||
akka.http.scaladsl.model.MediaType.Encoding encoding,
|
||||
Iterable<String> fileExtensions,
|
||||
Map<String, String> params) {
|
||||
return akka.http.scaladsl.model.MediaType.custom(mainType, subType, encoding, compressible, Util.<String, String>convertIterable(fileExtensions), Util.convertMapToScala(params), false);
|
||||
public static MediaType custom(String value, boolean binary, boolean compressible) {
|
||||
akka.http.scaladsl.model.MediaType.Compressibility comp = compressible ?
|
||||
akka.http.scaladsl.model.MediaType.Compressible$.MODULE$ : akka.http.scaladsl.model.MediaType.NotCompressible$.MODULE$;
|
||||
return akka.http.scaladsl.model.MediaType.custom(value, binary, comp , List.<String>empty());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ private[parser] trait AcceptHeader { this: Parser with CommonRules with CommonAc
|
|||
}
|
||||
} else {
|
||||
val (p, q) = MediaRange.splitOffQValue(params.toMap)
|
||||
MediaRange(getMediaType(main, sub, p), q)
|
||||
MediaRange(getMediaType(main, sub, p contains "charset", p), q)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,22 +12,28 @@ private[parser] trait CommonActions {
|
|||
|
||||
type StringMapBuilder = scala.collection.mutable.Builder[(String, String), Map[String, String]]
|
||||
|
||||
def getMediaType(mainType: String, subType: String, params: Map[String, String]): MediaType = {
|
||||
def getMediaType(mainType: String, subType: String, charsetDefined: Boolean,
|
||||
params: Map[String, String]): MediaType = {
|
||||
val subLower = subType.toRootLowerCase
|
||||
mainType.toRootLowerCase match {
|
||||
case "multipart" ⇒ subType.toRootLowerCase match {
|
||||
case "multipart" ⇒ subLower match {
|
||||
case "mixed" ⇒ multipart.mixed(params)
|
||||
case "alternative" ⇒ multipart.alternative(params)
|
||||
case "related" ⇒ multipart.related(params)
|
||||
case "form-data" ⇒ multipart.`form-data`(params)
|
||||
case "signed" ⇒ multipart.signed(params)
|
||||
case "encrypted" ⇒ multipart.encrypted(params)
|
||||
case custom ⇒ multipart(custom, params)
|
||||
case custom ⇒ MediaType.customMultipart(custom, params)
|
||||
}
|
||||
case mainLower ⇒
|
||||
MediaTypes.getForKey((mainLower, subType.toRootLowerCase)) match {
|
||||
MediaTypes.getForKey((mainLower, subLower)) match {
|
||||
case Some(registered) ⇒ if (params.isEmpty) registered else registered.withParams(params)
|
||||
case None ⇒ MediaType.custom(mainType, subType, encoding = MediaType.Encoding.Open,
|
||||
params = params, allowArbitrarySubtypes = true)
|
||||
case None ⇒
|
||||
if (charsetDefined)
|
||||
MediaType.customWithOpenCharset(mainLower, subType, params = params, allowArbitrarySubtypes = true)
|
||||
else
|
||||
MediaType.customBinary(mainLower, subType, MediaType.Compressible, params = params,
|
||||
allowArbitrarySubtypes = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,8 +23,14 @@ private[parser] trait ContentTypeHeader { this: Parser with CommonRules with Com
|
|||
params match {
|
||||
case Nil ⇒
|
||||
val parameters = if (builder eq null) Map.empty[String, String] else builder.result()
|
||||
val mediaType = getMediaType(main, sub, parameters)
|
||||
ContentType(mediaType, charset)
|
||||
getMediaType(main, sub, charset.isDefined, parameters) match {
|
||||
case x: MediaType.Binary ⇒ ContentType.Binary(x)
|
||||
case x: MediaType.WithFixedCharset ⇒ ContentType.WithFixedCharset(x)
|
||||
case x: MediaType.WithOpenCharset ⇒
|
||||
// if we have an open charset media-type but no charset parameter we default to UTF-8
|
||||
val cs = if (charset.isDefined) charset.get else HttpCharsets.`UTF-8`
|
||||
ContentType.WithCharset(x, cs)
|
||||
}
|
||||
|
||||
case Seq(("charset", value), tail @ _*) ⇒
|
||||
contentType(main, sub, tail, Some(getCharset(value)), builder)
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ private[parser] trait LinkHeader { this: Parser with CommonRules with CommonActi
|
|||
}
|
||||
}
|
||||
|
||||
def `link-media-type` = rule { `media-type` ~> ((mt, st, pm) ⇒ getMediaType(mt, st, pm.toMap)) }
|
||||
def `link-media-type` = rule { `media-type` ~> ((mt, st, pm) ⇒ getMediaType(mt, st, pm contains "charset", pm.toMap)) }
|
||||
|
||||
// filter out subsequent `rel`, `media`, `title`, `type` and `type*` params
|
||||
@tailrec private def sanitize(params: Seq[LinkParam], result: Seq[LinkParam] = Nil, seenRel: Boolean = false,
|
||||
|
|
|
|||
|
|
@ -155,6 +155,10 @@ private[http] object JavaMapping {
|
|||
implicit object DateTime extends Inherited[jm.DateTime, akka.http.scaladsl.model.DateTime]
|
||||
|
||||
implicit object ContentType extends Inherited[jm.ContentType, sm.ContentType]
|
||||
implicit object ContentTypeBinary extends Inherited[jm.ContentType.Binary, sm.ContentType.Binary]
|
||||
implicit object ContentTypeNonBinary extends Inherited[jm.ContentType.NonBinary, sm.ContentType.NonBinary]
|
||||
implicit object ContentTypeWithFixedCharset extends Inherited[jm.ContentType.WithFixedCharset, sm.ContentType.WithFixedCharset]
|
||||
implicit object ContentTypeWithCharset extends Inherited[jm.ContentType.WithCharset, sm.ContentType.WithCharset]
|
||||
implicit object ContentTypeRange extends Inherited[jm.ContentTypeRange, sm.ContentTypeRange]
|
||||
implicit object Host extends Inherited[jm.Host, sm.Uri.Host]
|
||||
implicit object HttpCharset extends Inherited[jm.HttpCharset, sm.HttpCharset]
|
||||
|
|
@ -167,6 +171,10 @@ private[http] object JavaMapping {
|
|||
implicit object HttpResponse extends Inherited[jm.HttpResponse, sm.HttpResponse]
|
||||
implicit object MediaRange extends Inherited[jm.MediaRange, sm.MediaRange]
|
||||
implicit object MediaType extends Inherited[jm.MediaType, sm.MediaType]
|
||||
implicit object MediaTypeBinary extends Inherited[jm.MediaType.Binary, sm.MediaType.Binary]
|
||||
implicit object MediaTypeNonBinary extends Inherited[jm.MediaType.NonBinary, sm.MediaType.NonBinary]
|
||||
implicit object MediaTypeFixedCharset extends Inherited[jm.MediaType.WithFixedCharset, sm.MediaType.WithFixedCharset]
|
||||
implicit object MediaTypeOpenCharset extends Inherited[jm.MediaType.WithOpenCharset, sm.MediaType.WithOpenCharset]
|
||||
implicit object StatusCode extends Inherited[jm.StatusCode, sm.StatusCode]
|
||||
|
||||
implicit object ContentRange extends Inherited[jm.ContentRange, sm.ContentRange]
|
||||
|
|
|
|||
|
|
@ -12,17 +12,15 @@ import akka.http.impl.util.JavaMapping.Implicits._
|
|||
|
||||
final case class ContentTypeRange(mediaRange: MediaRange, charsetRange: HttpCharsetRange) extends jm.ContentTypeRange with ValueRenderable {
|
||||
def matches(contentType: jm.ContentType) =
|
||||
mediaRange.matches(contentType.mediaType) && charsetRange.matches(contentType.charset)
|
||||
contentType match {
|
||||
case ContentType.Binary(mt) ⇒ mediaRange.matches(mt)
|
||||
case x: ContentType.NonBinary ⇒ mediaRange.matches(x.mediaType) && charsetRange.matches(x.charset)
|
||||
}
|
||||
|
||||
def render[R <: Rendering](r: R): r.type = charsetRange match {
|
||||
case HttpCharsetRange.`*` ⇒ r ~~ mediaRange
|
||||
case x ⇒ r ~~ mediaRange ~~ ContentType.`; charset=` ~~ x
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a [[ContentType]] instance which fits this range.
|
||||
*/
|
||||
def specimen: ContentType = ContentType(mediaRange.specimen, charsetRange.specimen)
|
||||
}
|
||||
|
||||
object ContentTypeRange {
|
||||
|
|
@ -31,74 +29,82 @@ object ContentTypeRange {
|
|||
implicit def apply(mediaType: MediaType): ContentTypeRange = apply(mediaType, HttpCharsetRange.`*`)
|
||||
implicit def apply(mediaRange: MediaRange): ContentTypeRange = apply(mediaRange, HttpCharsetRange.`*`)
|
||||
implicit def apply(contentType: ContentType): ContentTypeRange =
|
||||
contentType.definedCharset match {
|
||||
case Some(charset) ⇒ apply(contentType.mediaType, charset)
|
||||
case None ⇒ ContentTypeRange(contentType.mediaType)
|
||||
contentType match {
|
||||
case ContentType.Binary(mt) ⇒ ContentTypeRange(mt)
|
||||
case ContentType.WithFixedCharset(mt) ⇒ ContentTypeRange(mt)
|
||||
case ContentType.WithCharset(mt, cs) ⇒ ContentTypeRange(mt, cs)
|
||||
}
|
||||
}
|
||||
|
||||
abstract case class ContentType private (mediaType: MediaType, definedCharset: Option[HttpCharset]) extends jm.ContentType with ValueRenderable {
|
||||
private[http] def render[R <: Rendering](r: R): r.type = definedCharset match {
|
||||
case Some(cs) ⇒ r ~~ mediaType ~~ ContentType.`; charset=` ~~ cs
|
||||
case _ ⇒ r ~~ mediaType
|
||||
}
|
||||
def charset: HttpCharset = definedCharset orElse mediaType.encoding.charset getOrElse HttpCharsets.`UTF-8`
|
||||
/**
|
||||
* A `ContentType` represents a specific MediaType / HttpCharset combination.
|
||||
*
|
||||
* If the MediaType is not flexible with regard to the charset used, e.g. because it's a binary MediaType or
|
||||
* the charset is fixed, then the `ContentType` is a simple wrapper.
|
||||
*/
|
||||
sealed trait ContentType extends jm.ContentType with ValueRenderable {
|
||||
def mediaType: MediaType
|
||||
def charsetOption: Option[HttpCharset]
|
||||
|
||||
def hasOpenCharset: Boolean = definedCharset.isEmpty && mediaType.encoding == MediaType.Encoding.Open
|
||||
|
||||
def withMediaType(mediaType: MediaType) =
|
||||
if (mediaType != this.mediaType) ContentType(mediaType, definedCharset) else this
|
||||
def withCharset(charset: HttpCharset) =
|
||||
if (definedCharset.isEmpty || charset != definedCharset.get) ContentType(mediaType, charset) else this
|
||||
def withoutDefinedCharset =
|
||||
if (definedCharset.isDefined) ContentType(mediaType, None) else this
|
||||
def withDefaultCharset(charset: HttpCharset) =
|
||||
if (mediaType.encoding == MediaType.Encoding.Open && definedCharset.isEmpty) ContentType(mediaType, charset) else this
|
||||
private[http] def render[R <: Rendering](r: R): r.type = r ~~ mediaType
|
||||
|
||||
/** Java API */
|
||||
def getDefinedCharset: JOption[jm.HttpCharset] = definedCharset.asJava
|
||||
def getCharsetOption: JOption[jm.HttpCharset] = charsetOption.asJava
|
||||
}
|
||||
|
||||
object ContentType {
|
||||
private[http] case object `; charset=` extends SingletonValueRenderable
|
||||
|
||||
implicit def apply(mediaType: MediaType): ContentType = apply(mediaType, None)
|
||||
|
||||
def apply(mediaType: MediaType, charset: HttpCharset): ContentType = apply(mediaType, Some(charset))
|
||||
|
||||
def apply(mediaType: MediaType, charset: Option[HttpCharset]): ContentType = {
|
||||
val definedCharset =
|
||||
charset match {
|
||||
case None ⇒ None
|
||||
case Some(cs) ⇒ mediaType.encoding match {
|
||||
case MediaType.Encoding.Open ⇒ charset
|
||||
case MediaType.Encoding.Fixed(`cs`) ⇒ None
|
||||
case x ⇒ throw new IllegalArgumentException(
|
||||
s"MediaType $mediaType has a $x encoding and doesn't allow a custom `charset` $cs")
|
||||
}
|
||||
}
|
||||
new ContentType(mediaType, definedCharset) {}
|
||||
final case class Binary(mediaType: MediaType.Binary) extends jm.ContentType.Binary with ContentType {
|
||||
def binary = true
|
||||
def charsetOption = None
|
||||
}
|
||||
|
||||
sealed trait NonBinary extends jm.ContentType.NonBinary with ContentType {
|
||||
def binary = false
|
||||
def charset: HttpCharset
|
||||
def charsetOption = Some(charset)
|
||||
}
|
||||
|
||||
final case class WithFixedCharset(val mediaType: MediaType.WithFixedCharset)
|
||||
extends jm.ContentType.WithFixedCharset with NonBinary {
|
||||
def charset = mediaType.charset
|
||||
}
|
||||
|
||||
final case class WithCharset(val mediaType: MediaType.WithOpenCharset, val charset: HttpCharset)
|
||||
extends jm.ContentType.WithCharset with NonBinary {
|
||||
|
||||
private[http] override def render[R <: Rendering](r: R): r.type =
|
||||
super.render(r) ~~ ContentType.`; charset=` ~~ charset
|
||||
}
|
||||
|
||||
implicit def apply(mediaType: MediaType.Binary): Binary = Binary(mediaType)
|
||||
implicit def apply(mediaType: MediaType.WithFixedCharset): WithFixedCharset = WithFixedCharset(mediaType)
|
||||
def apply(mediaType: MediaType.WithOpenCharset, charset: HttpCharset): WithCharset = WithCharset(mediaType, charset)
|
||||
def apply(mediaType: MediaType, charset: () ⇒ HttpCharset): ContentType =
|
||||
mediaType match {
|
||||
case x: MediaType.Binary ⇒ ContentType(x)
|
||||
case x: MediaType.WithFixedCharset ⇒ ContentType(x)
|
||||
case x: MediaType.WithOpenCharset ⇒ ContentType(x, charset())
|
||||
}
|
||||
|
||||
def unapply(contentType: ContentType): Option[(MediaType, Option[HttpCharset])] =
|
||||
Some(contentType.mediaType → contentType.charsetOption)
|
||||
|
||||
/**
|
||||
* Tries to parse a ``ContentType`` value from the given String. Returns ``Right(contentType)`` if successful and
|
||||
* ``Left(errors)`` otherwise.
|
||||
*/
|
||||
def parse(value: String): Either[List[ErrorInfo], ContentType] =
|
||||
headers.`Content-Type`.parseFromValueString(value).right.map(_.contentType)
|
||||
|
||||
private[http] case object `; charset=` extends SingletonValueRenderable
|
||||
}
|
||||
|
||||
object ContentTypes {
|
||||
val `application/json` = ContentType(MediaTypes.`application/json`)
|
||||
val `application/octet-stream` = ContentType(MediaTypes.`application/octet-stream`)
|
||||
|
||||
val `text/plain` = ContentType(MediaTypes.`text/plain`)
|
||||
val `text/plain(UTF-8)` = ContentType(MediaTypes.`text/plain`, HttpCharsets.`UTF-8`)
|
||||
val `text/html` = ContentType(MediaTypes.`text/html`)
|
||||
val `text/xml` = ContentType(MediaTypes.`text/xml`)
|
||||
|
||||
val `application/x-www-form-urlencoded` = ContentType(MediaTypes.`application/x-www-form-urlencoded`)
|
||||
val `multipart/form-data` = ContentType(MediaTypes.`multipart/form-data`)
|
||||
val `text/plain(UTF-8)` = MediaTypes.`text/plain` withCharset HttpCharsets.`UTF-8`
|
||||
val `text/html(UTF-8)` = MediaTypes.`text/html` withCharset HttpCharsets.`UTF-8`
|
||||
val `text/xml(UTF-8)` = MediaTypes.`text/xml` withCharset HttpCharsets.`UTF-8`
|
||||
|
||||
// used for explicitly suppressing the rendering of Content-Type headers on requests and responses
|
||||
val NoContentType = ContentType(MediaTypes.NoMediaType)
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@ package akka.http.scaladsl.model
|
|||
|
||||
import akka.http.impl.model.parser.CharacterClasses
|
||||
import akka.http.impl.util.StringRendering
|
||||
import akka.http.javadsl.{ model ⇒ jm }
|
||||
import akka.http.scaladsl.model.HttpCharsets._
|
||||
import akka.http.scaladsl.model.MediaTypes._
|
||||
|
||||
/**
|
||||
|
|
@ -19,7 +17,7 @@ final case class FormData(fields: Uri.Query) {
|
|||
|
||||
def toEntity(charset: HttpCharset): akka.http.scaladsl.model.RequestEntity = {
|
||||
val render: StringRendering = UriRendering.renderQuery(new StringRendering, this.fields, charset.nioCharset, CharacterClasses.unreserved)
|
||||
HttpEntity(ContentType(`application/x-www-form-urlencoded`, `UTF-8`), render.get)
|
||||
HttpEntity(`application/x-www-form-urlencoded` withCharset charset, render.get)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,11 +20,6 @@ sealed abstract class HttpCharsetRange extends jm.HttpCharsetRange with ValueRen
|
|||
def qValue: Float
|
||||
def matches(charset: HttpCharset): Boolean
|
||||
|
||||
/**
|
||||
* Returns a [[HttpCharset]] instance which fits this range.
|
||||
*/
|
||||
def specimen: HttpCharset
|
||||
|
||||
/** Java API */
|
||||
def matches(charset: jm.HttpCharset): Boolean = {
|
||||
import akka.http.impl.util.JavaMapping.Implicits._
|
||||
|
|
@ -37,8 +32,6 @@ object HttpCharsetRange {
|
|||
require(0.0f <= qValue && qValue <= 1.0f, "qValue must be >= 0 and <= 1.0")
|
||||
final def render[R <: Rendering](r: R): r.type = if (qValue < 1.0f) r ~~ "*;q=" ~~ qValue else r ~~ '*'
|
||||
def matches(charset: HttpCharset) = true
|
||||
def matchesAll: Boolean = true
|
||||
def specimen: HttpCharset = HttpCharsets.`UTF-8`
|
||||
def withQValue(qValue: Float) =
|
||||
if (qValue == 1.0f) `*` else if (qValue != this.qValue) `*`(qValue.toFloat) else this
|
||||
}
|
||||
|
|
@ -47,8 +40,6 @@ object HttpCharsetRange {
|
|||
final case class One(charset: HttpCharset, qValue: Float) extends HttpCharsetRange {
|
||||
require(0.0f <= qValue && qValue <= 1.0f, "qValue must be >= 0 and <= 1.0")
|
||||
def matches(charset: HttpCharset) = this.charset.value.equalsIgnoreCase(charset.value)
|
||||
def matchesAll: Boolean = false
|
||||
def specimen: HttpCharset = charset
|
||||
def withQValue(qValue: Float) = One(charset, qValue)
|
||||
def render[R <: Rendering](r: R): r.type = if (qValue < 1.0f) r ~~ charset ~~ ";q=" ~~ qValue else r ~~ charset
|
||||
}
|
||||
|
|
|
|||
|
|
@ -178,7 +178,7 @@ object HttpEntity {
|
|||
implicit def apply(string: String): Strict = apply(ContentTypes.`text/plain(UTF-8)`, string)
|
||||
implicit def apply(bytes: Array[Byte]): Strict = apply(ContentTypes.`application/octet-stream`, bytes)
|
||||
implicit def apply(data: ByteString): Strict = apply(ContentTypes.`application/octet-stream`, data)
|
||||
def apply(contentType: ContentType, string: String): Strict =
|
||||
def apply(contentType: ContentType.NonBinary, string: String): Strict =
|
||||
if (string.isEmpty) empty(contentType) else apply(contentType, ByteString(string.getBytes(contentType.charset.nioCharset)))
|
||||
def apply(contentType: ContentType, bytes: Array[Byte]): Strict =
|
||||
if (bytes.length == 0) empty(contentType) else apply(contentType, ByteString(bytes))
|
||||
|
|
|
|||
|
|
@ -100,7 +100,8 @@ sealed trait HttpMessage extends jm.HttpMessage {
|
|||
def withEntity(string: String): Self = withEntity(HttpEntity(string))
|
||||
def withEntity(bytes: Array[Byte]): Self = withEntity(HttpEntity(bytes))
|
||||
def withEntity(bytes: ByteString): Self = withEntity(HttpEntity(bytes))
|
||||
def withEntity(contentType: jm.ContentType, string: String): Self = withEntity(HttpEntity(contentType.asInstanceOf[ContentType], string))
|
||||
def withEntity(contentType: jm.ContentType.NonBinary, string: String): Self =
|
||||
withEntity(HttpEntity(contentType.asInstanceOf[ContentType.NonBinary], string))
|
||||
def withEntity(contentType: jm.ContentType, bytes: Array[Byte]): Self = withEntity(HttpEntity(contentType.asInstanceOf[ContentType], bytes))
|
||||
def withEntity(contentType: jm.ContentType, bytes: ByteString): Self = withEntity(HttpEntity(contentType.asInstanceOf[ContentType], bytes))
|
||||
def withEntity(contentType: jm.ContentType, file: java.io.File): Self = withEntity(HttpEntity(contentType.asInstanceOf[ContentType], file))
|
||||
|
|
@ -163,115 +164,11 @@ final case class HttpRequest(method: HttpMethod = HttpMethods.GET,
|
|||
def withEffectiveUri(securedConnection: Boolean, defaultHostHeader: Host = Host.empty): HttpRequest =
|
||||
copy(uri = effectiveUri(securedConnection, defaultHostHeader))
|
||||
|
||||
/**
|
||||
* The media-ranges accepted by the client according to the `Accept` request header.
|
||||
* The returned ranges are sorted by decreasing q-value.
|
||||
*/
|
||||
def acceptedMediaRanges: immutable.Seq[MediaRange] =
|
||||
(for {
|
||||
Accept(mediaRanges) ← headers
|
||||
range ← mediaRanges
|
||||
} yield range).sortBy(-_.qValue)
|
||||
|
||||
/**
|
||||
* The charset-ranges accepted by the client according to the `Accept-Charset` request header.
|
||||
* The returned ranges are sorted by decreasing q-value.
|
||||
*/
|
||||
def acceptedCharsetRanges: immutable.Seq[HttpCharsetRange] =
|
||||
(for {
|
||||
`Accept-Charset`(charsetRanges) ← headers
|
||||
range ← charsetRanges
|
||||
} yield range).sortBy(-_.qValue)
|
||||
|
||||
/**
|
||||
* The encoding-ranges accepted by the client according to the `Accept-Encoding` request header.
|
||||
* The returned ranges are sorted by decreasing q-value.
|
||||
*/
|
||||
def acceptedEncodingRanges: immutable.Seq[HttpEncodingRange] =
|
||||
(for {
|
||||
`Accept-Encoding`(encodingRanges) ← headers
|
||||
range ← encodingRanges
|
||||
} yield range).sortBy(-_.qValue)
|
||||
|
||||
/**
|
||||
* The language-ranges accepted by the client according to the `Accept-Language` request header.
|
||||
* The returned ranges are sorted by increasing generality (i.e. most specific first).
|
||||
*/
|
||||
def acceptedLanguageRanges: immutable.Seq[LanguageRange] =
|
||||
(for {
|
||||
`Accept-Language`(languageRanges) ← headers
|
||||
range ← languageRanges
|
||||
} yield range).sortBy {
|
||||
case _: LanguageRange.`*` ⇒ 0 // most general, needs to come last
|
||||
case x ⇒ -(x.subTags.size + 1) // more subtags -> more specific -> go first
|
||||
}
|
||||
|
||||
/**
|
||||
* All cookies provided by the client in one or more `Cookie` headers.
|
||||
*/
|
||||
def cookies: immutable.Seq[HttpCookiePair] = for (`Cookie`(cookies) ← headers; cookie ← cookies) yield cookie
|
||||
|
||||
/**
|
||||
* Determines whether the given media-type is accepted by the client.
|
||||
*/
|
||||
def isMediaTypeAccepted(mediaType: MediaType, ranges: Seq[MediaRange] = acceptedMediaRanges): Boolean =
|
||||
qValueForMediaType(mediaType, ranges) > 0f
|
||||
|
||||
/**
|
||||
* Returns the q-value that the client (implicitly or explicitly) attaches to the given media-type.
|
||||
*/
|
||||
def qValueForMediaType(mediaType: MediaType, ranges: Seq[MediaRange] = acceptedMediaRanges): Float =
|
||||
ranges match {
|
||||
case Nil ⇒ 1.0f // http://tools.ietf.org/html/rfc7231#section-5.3.1
|
||||
case x ⇒ x collectFirst { case r if r matches mediaType ⇒ r.qValue } getOrElse 0f
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the given charset is accepted by the client.
|
||||
*/
|
||||
def isCharsetAccepted(charset: HttpCharset, ranges: Seq[HttpCharsetRange] = acceptedCharsetRanges): Boolean =
|
||||
qValueForCharset(charset, ranges) > 0f
|
||||
|
||||
/**
|
||||
* Returns the q-value that the client (implicitly or explicitly) attaches to the given charset.
|
||||
*/
|
||||
def qValueForCharset(charset: HttpCharset, ranges: Seq[HttpCharsetRange] = acceptedCharsetRanges): Float =
|
||||
ranges match {
|
||||
case Nil ⇒ 1.0f // http://tools.ietf.org/html/rfc7231#section-5.3.1
|
||||
case x ⇒ x collectFirst { case r if r matches charset ⇒ r.qValue } getOrElse 0f
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the given encoding is accepted by the client.
|
||||
*/
|
||||
def isEncodingAccepted(encoding: HttpEncoding, ranges: Seq[HttpEncodingRange] = acceptedEncodingRanges): Boolean =
|
||||
qValueForEncoding(encoding, ranges) > 0f
|
||||
|
||||
/**
|
||||
* Returns the q-value that the client (implicitly or explicitly) attaches to the given encoding.
|
||||
*/
|
||||
def qValueForEncoding(encoding: HttpEncoding, ranges: Seq[HttpEncodingRange] = acceptedEncodingRanges): Float =
|
||||
ranges match {
|
||||
case Nil ⇒ 1.0f // http://tools.ietf.org/html/rfc7231#section-5.3.1
|
||||
case x ⇒ x collectFirst { case r if r matches encoding ⇒ r.qValue } getOrElse 0f
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the given language is accepted by the client.
|
||||
*/
|
||||
def isLanguageAccepted(language: Language, ranges: Seq[LanguageRange] = acceptedLanguageRanges): Boolean =
|
||||
qValueForLanguage(language, ranges) > 0f
|
||||
|
||||
/**
|
||||
* Returns the q-value that the client (implicitly or explicitly) attaches to the given language.
|
||||
* Note: The given ranges must be sorted by increasing generality (i.e. most specific first)!
|
||||
*/
|
||||
def qValueForLanguage(language: Language, ranges: Seq[LanguageRange] = acceptedLanguageRanges): Float =
|
||||
ranges match {
|
||||
case Nil ⇒ 1.0f // http://tools.ietf.org/html/rfc7231#section-5.3.1
|
||||
case x ⇒ x collectFirst { case r if r matches language ⇒ r.qValue } getOrElse 0f
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether this request can be safely retried, which is the case only of the request method is idempotent.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,148 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.http.scaladsl.model
|
||||
|
||||
import language.implicitConversions
|
||||
import java.util
|
||||
import akka.http.impl.util._
|
||||
import akka.http.javadsl.{ model ⇒ jm }
|
||||
|
||||
sealed abstract class MediaRange extends jm.MediaRange with Renderable with WithQValue[MediaRange] {
|
||||
def value: String
|
||||
def mainType: String
|
||||
def params: Map[String, String]
|
||||
def qValue: Float
|
||||
def matches(mediaType: MediaType): Boolean
|
||||
def isApplication = false
|
||||
def isAudio = false
|
||||
def isImage = false
|
||||
def isMessage = false
|
||||
def isMultipart = false
|
||||
def isText = false
|
||||
def isVideo = false
|
||||
def isWildcard = mainType == "*"
|
||||
|
||||
/**
|
||||
* Returns a copy of this instance with the params replaced by the given ones.
|
||||
* If the given map contains a "q" value the `qValue` member is (also) updated.
|
||||
*/
|
||||
def withParams(params: Map[String, String]): MediaRange
|
||||
|
||||
/**
|
||||
* Constructs a `ContentTypeRange` from this instance and the given charset.
|
||||
*/
|
||||
def withCharsetRange(charsetRange: HttpCharsetRange): ContentTypeRange = ContentTypeRange(this, charsetRange)
|
||||
|
||||
/** Java API */
|
||||
def getParams: util.Map[String, String] = {
|
||||
import collection.JavaConverters._
|
||||
params.asJava
|
||||
}
|
||||
/** Java API */
|
||||
def matches(mediaType: jm.MediaType): Boolean = {
|
||||
import akka.http.impl.util.JavaMapping.Implicits._
|
||||
matches(mediaType.asScala)
|
||||
}
|
||||
}
|
||||
|
||||
object MediaRange {
|
||||
private[http] def splitOffQValue(params: Map[String, String], defaultQ: Float = 1.0f): (Map[String, String], Float) =
|
||||
params.get("q") match {
|
||||
case Some(x) ⇒ (params - "q") -> (try x.toFloat catch { case _: NumberFormatException ⇒ 1.0f })
|
||||
case None ⇒ params -> defaultQ
|
||||
}
|
||||
|
||||
private final case class Custom(mainType: String, params: Map[String, String], qValue: Float)
|
||||
extends MediaRange with ValueRenderable {
|
||||
require(0.0f <= qValue && qValue <= 1.0f, "qValue must be >= 0 and <= 1.0")
|
||||
def matches(mediaType: MediaType) = mainType == "*" || mediaType.mainType == mainType
|
||||
def withParams(params: Map[String, String]) = custom(mainType, params, qValue)
|
||||
def withQValue(qValue: Float) = if (qValue != this.qValue) custom(mainType, params, qValue) else this
|
||||
def render[R <: Rendering](r: R): r.type = {
|
||||
r ~~ mainType ~~ '/' ~~ '*'
|
||||
if (qValue < 1.0f) r ~~ ";q=" ~~ qValue
|
||||
if (params.nonEmpty) params foreach { case (k, v) ⇒ r ~~ ';' ~~ ' ' ~~ k ~~ '=' ~~# v }
|
||||
r
|
||||
}
|
||||
override def isApplication = mainType == "application"
|
||||
override def isAudio = mainType == "audio"
|
||||
override def isImage = mainType == "image"
|
||||
override def isMessage = mainType == "message"
|
||||
override def isMultipart = mainType == "multipart"
|
||||
override def isText = mainType == "text"
|
||||
override def isVideo = mainType == "video"
|
||||
}
|
||||
|
||||
def custom(mainType: String, params: Map[String, String] = Map.empty, qValue: Float = 1.0f): MediaRange = {
|
||||
val (ps, q) = splitOffQValue(params, qValue)
|
||||
Custom(mainType.toRootLowerCase, ps, q)
|
||||
}
|
||||
|
||||
final case class One(mediaType: MediaType, qValue: Float) extends MediaRange with ValueRenderable {
|
||||
require(0.0f <= qValue && qValue <= 1.0f, "qValue must be >= 0 and <= 1.0")
|
||||
def mainType = mediaType.mainType
|
||||
def params = mediaType.params
|
||||
override def isApplication = mediaType.isApplication
|
||||
override def isAudio = mediaType.isAudio
|
||||
override def isImage = mediaType.isImage
|
||||
override def isMessage = mediaType.isMessage
|
||||
override def isMultipart = mediaType.isMultipart
|
||||
override def isText = mediaType.isText
|
||||
override def isVideo = mediaType.isVideo
|
||||
def matches(mediaType: MediaType) =
|
||||
this.mediaType.mainType == mediaType.mainType && this.mediaType.subType == mediaType.subType
|
||||
def withParams(params: Map[String, String]) = copy(mediaType = mediaType.withParams(params))
|
||||
def withQValue(qValue: Float) = copy(qValue = qValue)
|
||||
def render[R <: Rendering](r: R): r.type = if (qValue < 1.0f) r ~~ mediaType ~~ ";q=" ~~ qValue else r ~~ mediaType
|
||||
}
|
||||
|
||||
implicit def apply(mediaType: MediaType): MediaRange = apply(mediaType, 1.0f)
|
||||
def apply(mediaType: MediaType, qValue: Float = 1.0f): MediaRange = One(mediaType, qValue)
|
||||
}
|
||||
|
||||
object MediaRanges extends ObjectRegistry[String, MediaRange] {
|
||||
|
||||
sealed abstract case class PredefinedMediaRange(value: String) extends MediaRange with LazyValueBytesRenderable {
|
||||
val mainType = value takeWhile (_ != '/')
|
||||
register(mainType, this)
|
||||
def params = Map.empty
|
||||
def qValue = 1.0f
|
||||
def withParams(params: Map[String, String]) = MediaRange.custom(mainType, params)
|
||||
def withQValue(qValue: Float) = if (qValue != 1.0f) MediaRange.custom(mainType, params, qValue) else this
|
||||
}
|
||||
|
||||
val `*/*` = new PredefinedMediaRange("*/*") {
|
||||
def matches(mediaType: MediaType) = true
|
||||
}
|
||||
val `*/*;q=MIN` = `*/*`.withQValue(Float.MinPositiveValue)
|
||||
val `application/*` = new PredefinedMediaRange("application/*") {
|
||||
def matches(mediaType: MediaType) = mediaType.isApplication
|
||||
override def isApplication = true
|
||||
}
|
||||
val `audio/*` = new PredefinedMediaRange("audio/*") {
|
||||
def matches(mediaType: MediaType) = mediaType.isAudio
|
||||
override def isAudio = true
|
||||
}
|
||||
val `image/*` = new PredefinedMediaRange("image/*") {
|
||||
def matches(mediaType: MediaType) = mediaType.isImage
|
||||
override def isImage = true
|
||||
}
|
||||
val `message/*` = new PredefinedMediaRange("message/*") {
|
||||
def matches(mediaType: MediaType) = mediaType.isMessage
|
||||
override def isMessage = true
|
||||
}
|
||||
val `multipart/*` = new PredefinedMediaRange("multipart/*") {
|
||||
def matches(mediaType: MediaType) = mediaType.isMultipart
|
||||
override def isMultipart = true
|
||||
}
|
||||
val `text/*` = new PredefinedMediaRange("text/*") {
|
||||
def matches(mediaType: MediaType) = mediaType.isText
|
||||
override def isText = true
|
||||
}
|
||||
val `video/*` = new PredefinedMediaRange("video/*") {
|
||||
def matches(mediaType: MediaType) = mediaType.isVideo
|
||||
override def isVideo = true
|
||||
}
|
||||
}
|
||||
|
|
@ -5,243 +5,118 @@
|
|||
package akka.http.scaladsl.model
|
||||
|
||||
import language.implicitConversions
|
||||
import java.util
|
||||
import scala.collection.immutable
|
||||
import akka.http.impl.util._
|
||||
import akka.http.javadsl.{ model ⇒ jm }
|
||||
import akka.http.impl.util.JavaMapping.Implicits._
|
||||
|
||||
sealed abstract class MediaRange extends jm.MediaRange with Renderable with WithQValue[MediaRange] {
|
||||
def value: String
|
||||
def mainType: String
|
||||
/**
|
||||
* A MediaType describes the type of the content of an HTTP message entity.
|
||||
*
|
||||
* While knowledge of the MediaType alone suffices for being able to properly interpret binary content this
|
||||
* is not generally the case for non-binary (i.e. character-based) content, which also requires the definition
|
||||
* of a specific character encoding ([[HttpCharset]]).
|
||||
* Therefore [[MediaType]] instances are frequently encountered as a member of a [[ContentType]], which
|
||||
* groups a [[MediaType]] with a potentially required [[HttpCharset]] to hold everything required for being
|
||||
* able to interpret an [[HttpEntity]].
|
||||
*
|
||||
* MediaTypes come in three basic forms:
|
||||
*
|
||||
* 1. Binary: These do not need an additional [[HttpCharset]] to be able to form a [[ContentType]]. Therefore
|
||||
* they can be implicitly converted to the latter.
|
||||
*
|
||||
* 2. WithOpenCharset: Most character-based MediaTypes are of this form, which can be combined with all
|
||||
* [[HttpCharset]] instances to form a [[ContentType]].
|
||||
*
|
||||
* 3. WithFixedCharset: Some character-based MediaTypes prescribe a single, clearly defined charset and as such,
|
||||
* similarly to binary MediaTypes, do not require the addition of an [[HttpCharset]] instances to form a
|
||||
* [[ContentType]]. The most prominent example is probably `application/json` which must always be UTF-8 encoded.
|
||||
* Like binary MediaTypes `WithFixedCharset` types can be implicitly converted to a [[ContentType]].
|
||||
*/
|
||||
sealed abstract class MediaType extends jm.MediaType with LazyValueBytesRenderable with WithQValue[MediaRange] {
|
||||
import MediaType.Compressibility
|
||||
|
||||
def fileExtensions: List[String]
|
||||
def params: Map[String, String]
|
||||
def qValue: Float
|
||||
def matches(mediaType: MediaType): Boolean
|
||||
def isApplication = false
|
||||
def isAudio = false
|
||||
def isImage = false
|
||||
def isMessage = false
|
||||
def isMultipart = false
|
||||
def isText = false
|
||||
def isVideo = false
|
||||
def isWildcard = mainType == "*"
|
||||
def comp: Compressibility
|
||||
|
||||
/**
|
||||
* Returns a copy of this instance with the params replaced by the given ones.
|
||||
* If the given map contains a "q" value the `qValue` member is (also) updated.
|
||||
*/
|
||||
def withParams(params: Map[String, String]): MediaRange
|
||||
override def isApplication: Boolean = false
|
||||
override def isAudio: Boolean = false
|
||||
override def isImage: Boolean = false
|
||||
override def isMessage: Boolean = false
|
||||
override def isMultipart: Boolean = false
|
||||
override def isText: Boolean = false
|
||||
override def isVideo: Boolean = false
|
||||
|
||||
/**
|
||||
* Constructs a `ContentTypeRange` from this instance and the given charset.
|
||||
*/
|
||||
def withCharset(charsetRange: HttpCharsetRange): ContentTypeRange = ContentTypeRange(this, charsetRange)
|
||||
|
||||
/**
|
||||
* Returns a [[MediaType]] instance which fits this range.
|
||||
*/
|
||||
def specimen: MediaType
|
||||
|
||||
/** Java API */
|
||||
def getParams: util.Map[String, String] = {
|
||||
import collection.JavaConverters._
|
||||
params.asJava
|
||||
}
|
||||
/** Java API */
|
||||
def matches(mediaType: jm.MediaType): Boolean = {
|
||||
import akka.http.impl.util.JavaMapping.Implicits._
|
||||
matches(mediaType.asScala)
|
||||
}
|
||||
}
|
||||
|
||||
object MediaRange {
|
||||
private[http] def splitOffQValue(params: Map[String, String], defaultQ: Float = 1.0f): (Map[String, String], Float) =
|
||||
params.get("q") match {
|
||||
case Some(x) ⇒ (params - "q") -> (try x.toFloat catch { case _: NumberFormatException ⇒ 1.0f })
|
||||
case None ⇒ params -> defaultQ
|
||||
}
|
||||
|
||||
private final case class Custom(mainType: String, params: Map[String, String], qValue: Float)
|
||||
extends MediaRange with ValueRenderable {
|
||||
require(0.0f <= qValue && qValue <= 1.0f, "qValue must be >= 0 and <= 1.0")
|
||||
def matches(mediaType: MediaType) = mainType == "*" || mediaType.mainType == mainType
|
||||
def withParams(params: Map[String, String]) = custom(mainType, params, qValue)
|
||||
def withQValue(qValue: Float) = if (qValue != this.qValue) custom(mainType, params, qValue) else this
|
||||
def render[R <: Rendering](r: R): r.type = {
|
||||
r ~~ mainType ~~ '/' ~~ '*'
|
||||
if (qValue < 1.0f) r ~~ ";q=" ~~ qValue
|
||||
if (params.nonEmpty) params foreach { case (k, v) ⇒ r ~~ ';' ~~ ' ' ~~ k ~~ '=' ~~# v }
|
||||
r
|
||||
}
|
||||
override def isApplication = mainType == "application"
|
||||
override def isAudio = mainType == "audio"
|
||||
override def isImage = mainType == "image"
|
||||
override def isMessage = mainType == "message"
|
||||
override def isMultipart = mainType == "multipart"
|
||||
override def isText = mainType == "text"
|
||||
override def isVideo = mainType == "video"
|
||||
def specimen = MediaType.custom(mainType, "custom", MediaType.Encoding.Binary)
|
||||
}
|
||||
|
||||
def custom(mainType: String, params: Map[String, String] = Map.empty, qValue: Float = 1.0f): MediaRange = {
|
||||
val (ps, q) = splitOffQValue(params, qValue)
|
||||
Custom(mainType.toRootLowerCase, ps, q)
|
||||
}
|
||||
|
||||
final case class One(mediaType: MediaType, qValue: Float) extends MediaRange with ValueRenderable {
|
||||
require(0.0f <= qValue && qValue <= 1.0f, "qValue must be >= 0 and <= 1.0")
|
||||
def mainType = mediaType.mainType
|
||||
def params = mediaType.params
|
||||
override def isApplication = mediaType.isApplication
|
||||
override def isAudio = mediaType.isAudio
|
||||
override def isImage = mediaType.isImage
|
||||
override def isMessage = mediaType.isMessage
|
||||
override def isMultipart = mediaType.isMultipart
|
||||
override def isText = mediaType.isText
|
||||
override def isVideo = mediaType.isVideo
|
||||
def matches(mediaType: MediaType) =
|
||||
this.mediaType.mainType == mediaType.mainType && this.mediaType.subType == mediaType.subType
|
||||
def withParams(params: Map[String, String]) = copy(mediaType = mediaType.withParams(params))
|
||||
def withQValue(qValue: Float) = copy(qValue = qValue)
|
||||
def render[R <: Rendering](r: R): r.type = if (qValue < 1.0f) r ~~ mediaType ~~ ";q=" ~~ qValue else r ~~ mediaType
|
||||
def specimen = mediaType
|
||||
}
|
||||
|
||||
implicit def apply(mediaType: MediaType): MediaRange = apply(mediaType, 1.0f)
|
||||
def apply(mediaType: MediaType, qValue: Float = 1.0f): MediaRange = One(mediaType, qValue)
|
||||
}
|
||||
|
||||
object MediaRanges extends ObjectRegistry[String, MediaRange] {
|
||||
|
||||
sealed abstract case class PredefinedMediaRange(value: String) extends MediaRange with LazyValueBytesRenderable {
|
||||
val mainType = value takeWhile (_ != '/')
|
||||
register(mainType, this)
|
||||
def params = Map.empty
|
||||
def qValue = 1.0f
|
||||
def withParams(params: Map[String, String]) = MediaRange.custom(mainType, params)
|
||||
def withQValue(qValue: Float) = if (qValue != 1.0f) MediaRange.custom(mainType, params, qValue) else this
|
||||
}
|
||||
|
||||
val `*/*` = new PredefinedMediaRange("*/*") {
|
||||
def matches(mediaType: MediaType) = true
|
||||
def specimen = MediaTypes.`text/plain`
|
||||
}
|
||||
val `*/*;q=MIN` = `*/*`.withQValue(Float.MinPositiveValue)
|
||||
val `application/*` = new PredefinedMediaRange("application/*") {
|
||||
def matches(mediaType: MediaType) = mediaType.isApplication
|
||||
override def isApplication = true
|
||||
def specimen = MediaTypes.`application/json`
|
||||
}
|
||||
val `audio/*` = new PredefinedMediaRange("audio/*") {
|
||||
def matches(mediaType: MediaType) = mediaType.isAudio
|
||||
override def isAudio = true
|
||||
def specimen = MediaTypes.`audio/ogg`
|
||||
}
|
||||
val `image/*` = new PredefinedMediaRange("image/*") {
|
||||
def matches(mediaType: MediaType) = mediaType.isImage
|
||||
override def isImage = true
|
||||
def specimen = MediaTypes.`image/png`
|
||||
}
|
||||
val `message/*` = new PredefinedMediaRange("message/*") {
|
||||
def matches(mediaType: MediaType) = mediaType.isMessage
|
||||
override def isMessage = true
|
||||
def specimen = MediaTypes.`message/rfc822`
|
||||
}
|
||||
val `multipart/*` = new PredefinedMediaRange("multipart/*") {
|
||||
def matches(mediaType: MediaType) = mediaType.isMultipart
|
||||
override def isMultipart = true
|
||||
def specimen = MediaTypes.`multipart/form-data`
|
||||
}
|
||||
val `text/*` = new PredefinedMediaRange("text/*") {
|
||||
def matches(mediaType: MediaType) = mediaType.isText
|
||||
override def isText = true
|
||||
def specimen = MediaTypes.`text/plain`
|
||||
}
|
||||
val `video/*` = new PredefinedMediaRange("video/*") {
|
||||
def matches(mediaType: MediaType) = mediaType.isVideo
|
||||
override def isVideo = true
|
||||
def specimen = MediaTypes.`video/mp4`
|
||||
}
|
||||
}
|
||||
|
||||
sealed abstract case class MediaType private[http] (value: String)(val mainType: String,
|
||||
val subType: String,
|
||||
val compressible: Boolean,
|
||||
val encoding: MediaType.Encoding,
|
||||
val fileExtensions: immutable.Seq[String],
|
||||
val params: Map[String, String])
|
||||
extends jm.MediaType with LazyValueBytesRenderable with WithQValue[MediaRange] {
|
||||
def isApplication = false
|
||||
def isAudio = false
|
||||
def isImage = false
|
||||
def isMessage = false
|
||||
def isMultipart = false
|
||||
def isText = false
|
||||
def isVideo = false
|
||||
|
||||
/**
|
||||
* Returns a copy of this instance with the params replaced by the given ones.
|
||||
*/
|
||||
def withParams(params: Map[String, String]): MediaType
|
||||
def withComp(comp: Compressibility): MediaType
|
||||
def withQValue(qValue: Float): MediaRange = MediaRange(this, qValue.toFloat)
|
||||
|
||||
override def equals(that: Any): Boolean =
|
||||
that match {
|
||||
case x: MediaType ⇒ value equalsIgnoreCase x.value
|
||||
case _ ⇒ false
|
||||
}
|
||||
|
||||
override def hashCode(): Int = value.hashCode
|
||||
|
||||
/**
|
||||
* Constructs a `ContentType` from this instance and the given charset.
|
||||
* JAVA API
|
||||
*/
|
||||
def withCharset(charset: HttpCharset): ContentType = ContentType(this, charset)
|
||||
|
||||
def withQValue(qValue: Float): MediaRange = MediaRange(this, qValue.toFloat)
|
||||
}
|
||||
|
||||
class MultipartMediaType private[http] (_value: String, _subType: String, _params: Map[String, String])
|
||||
extends MediaType(_value)("multipart", _subType, compressible = true, encoding = MediaType.Encoding.Open, Nil, _params) {
|
||||
override def isMultipart = true
|
||||
def withBoundary(boundary: String): MultipartMediaType = withParams {
|
||||
if (boundary.isEmpty) params - "boundary" else params.updated("boundary", boundary)
|
||||
}
|
||||
def withParams(params: Map[String, String]) = MediaTypes.multipart(subType, params)
|
||||
}
|
||||
|
||||
sealed abstract class NonMultipartMediaType private[http] (_value: String, _mainType: String, _subType: String,
|
||||
_compressible: Boolean, _encoding: MediaType.Encoding,
|
||||
_fileExtensions: immutable.Seq[String],
|
||||
_params: Map[String, String])
|
||||
extends MediaType(_value)(_mainType, _subType, _compressible, _encoding, _fileExtensions, _params) {
|
||||
private[http] def this(mainType: String, subType: String, compressible: Boolean, encoding: MediaType.Encoding,
|
||||
fileExtensions: immutable.Seq[String]) =
|
||||
this(mainType + '/' + subType, mainType, subType, compressible, encoding, fileExtensions, Map.empty)
|
||||
def withParams(params: Map[String, String]) =
|
||||
MediaType.custom(mainType, subType, encoding, compressible, fileExtensions, params)
|
||||
def toRange = jm.MediaRanges.create(this)
|
||||
def toRange(qValue: Float) = jm.MediaRanges.create(this, qValue)
|
||||
def isCompressible: Boolean = comp.compressible
|
||||
}
|
||||
|
||||
object MediaType {
|
||||
sealed abstract class Encoding(val charset: Option[HttpCharset])
|
||||
object Encoding {
|
||||
/**
|
||||
* Indicates that the media type is non-textual and a character encoding therefore has no meaning.
|
||||
*/
|
||||
case object Binary extends Encoding(None)
|
||||
|
||||
/**
|
||||
* Indicates that the media-type allow for flexible character encoding through a `charset` parameter.
|
||||
*/
|
||||
case object Open extends Encoding(None)
|
||||
def applicationBinary(subType: String, comp: Compressibility, fileExtensions: String*): Binary =
|
||||
new Binary("application/" + subType, "application", subType, comp, fileExtensions.toList) {
|
||||
override def isApplication = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that a media-type is textual and mandates a clearly defined character encoding.
|
||||
*/
|
||||
final case class Fixed(cs: HttpCharset) extends Encoding(Some(cs))
|
||||
}
|
||||
def applicationWithFixedCharset(subType: String, charset: HttpCharset,
|
||||
fileExtensions: String*): WithFixedCharset =
|
||||
new WithFixedCharset("application/" + subType, "application", subType, charset, fileExtensions.toList) {
|
||||
override def isApplication = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a custom media type.
|
||||
*/
|
||||
def custom(mainType: String, subType: String, encoding: MediaType.Encoding,
|
||||
compressible: Boolean = false, fileExtensions: immutable.Seq[String] = Nil,
|
||||
params: Map[String, String] = Map.empty, allowArbitrarySubtypes: Boolean = false): MediaType = {
|
||||
require(mainType != "multipart", "Cannot create a MultipartMediaType here, use `multipart.apply` instead!")
|
||||
def applicationWithOpenCharset(subType: String, fileExtensions: String*): WithOpenCharset =
|
||||
new NonMultipartWithOpenCharset("application/" + subType, "application", subType, fileExtensions.toList) {
|
||||
override def isApplication = true
|
||||
}
|
||||
|
||||
def audio(subType: String, comp: Compressibility, fileExtensions: String*): Binary =
|
||||
new Binary("audio/" + subType, "audio", subType, comp, fileExtensions.toList) {
|
||||
override def isAudio = true
|
||||
}
|
||||
|
||||
def image(subType: String, comp: Compressibility, fileExtensions: String*): Binary =
|
||||
new Binary("image/" + subType, "image", subType, comp, fileExtensions.toList) {
|
||||
override def isImage = true
|
||||
}
|
||||
|
||||
def message(subType: String, comp: Compressibility, fileExtensions: String*): Binary =
|
||||
new Binary("message/" + subType, "message", subType, comp, fileExtensions.toList) {
|
||||
override def isMessage = true
|
||||
}
|
||||
|
||||
def text(subType: String, fileExtensions: String*): WithOpenCharset =
|
||||
new NonMultipartWithOpenCharset("text/" + subType, "text", subType, fileExtensions.toList) {
|
||||
override def isText = true
|
||||
}
|
||||
|
||||
def video(subType: String, comp: Compressibility, fileExtensions: String*): Binary =
|
||||
new Binary("video/" + subType, "video", subType, comp, fileExtensions.toList) {
|
||||
override def isVideo = true
|
||||
}
|
||||
|
||||
def customBinary(mainType: String, subType: String, comp: Compressibility, fileExtensions: List[String] = Nil,
|
||||
params: Map[String, String] = Map.empty, allowArbitrarySubtypes: Boolean = false): Binary = {
|
||||
require(mainType != "multipart", "Cannot create a MediaType.Multipart here, use `customMultipart` instead!")
|
||||
require(allowArbitrarySubtypes || subType != "*", "Cannot create a MediaRange here, use `MediaRange.custom` instead!")
|
||||
val r = new StringRendering ~~ mainType ~~ '/' ~~ subType
|
||||
if (params.nonEmpty) params foreach { case (k, v) ⇒ r ~~ ';' ~~ ' ' ~~ k ~~ '=' ~~# v }
|
||||
new NonMultipartMediaType(r.get, mainType, subType, compressible, encoding, fileExtensions, params) {
|
||||
val _params = params
|
||||
new Binary(renderValue(mainType, subType, params), mainType, subType, comp, fileExtensions) {
|
||||
override def params = _params
|
||||
override def isApplication = mainType == "application"
|
||||
override def isAudio = mainType == "audio"
|
||||
override def isImage = mainType == "image"
|
||||
|
|
@ -251,199 +126,290 @@ object MediaType {
|
|||
}
|
||||
}
|
||||
|
||||
def custom(value: String, encoding: MediaType.Encoding): MediaType = {
|
||||
def customWithFixedCharset(mainType: String, subType: String, charset: HttpCharset, fileExtensions: List[String] = Nil,
|
||||
params: Map[String, String] = Map.empty,
|
||||
allowArbitrarySubtypes: Boolean = false): WithFixedCharset = {
|
||||
require(mainType != "multipart", "Cannot create a MediaType.Multipart here, use `customMultipart` instead!")
|
||||
require(allowArbitrarySubtypes || subType != "*", "Cannot create a MediaRange here, use `MediaRange.custom` instead!")
|
||||
val _params = params
|
||||
new WithFixedCharset(renderValue(mainType, subType, params), mainType, subType, charset, fileExtensions) {
|
||||
override def params = _params
|
||||
override def isApplication = mainType == "application"
|
||||
override def isAudio = mainType == "audio"
|
||||
override def isImage = mainType == "image"
|
||||
override def isMessage = mainType == "message"
|
||||
override def isText = mainType == "text"
|
||||
override def isVideo = mainType == "video"
|
||||
}
|
||||
}
|
||||
|
||||
def customWithOpenCharset(mainType: String, subType: String, fileExtensions: List[String] = Nil,
|
||||
params: Map[String, String] = Map.empty,
|
||||
allowArbitrarySubtypes: Boolean = false): WithOpenCharset = {
|
||||
require(mainType != "multipart", "Cannot create a MediaType.Multipart here, use `customMultipart` instead!")
|
||||
require(allowArbitrarySubtypes || subType != "*", "Cannot create a MediaRange here, use `MediaRange.custom` instead!")
|
||||
val _params = params
|
||||
new NonMultipartWithOpenCharset(renderValue(mainType, subType, params), mainType, subType, fileExtensions) {
|
||||
override def params = _params
|
||||
override def isApplication = mainType == "application"
|
||||
override def isAudio = mainType == "audio"
|
||||
override def isImage = mainType == "image"
|
||||
override def isMessage = mainType == "message"
|
||||
override def isText = mainType == "text"
|
||||
override def isVideo = mainType == "video"
|
||||
}
|
||||
}
|
||||
|
||||
def customMultipart(subType: String, params: Map[String, String]): Multipart = {
|
||||
require(subType != "*", "Cannot create a MediaRange here, use MediaRanges.`multipart/*` instead!")
|
||||
new Multipart(subType, params)
|
||||
}
|
||||
|
||||
def custom(value: String, binary: Boolean, comp: Compressibility = Compressible,
|
||||
fileExtensions: List[String] = Nil): MediaType = {
|
||||
val parts = value.split('/')
|
||||
if (parts.length != 2) throw new IllegalArgumentException(value + " is not a valid media-type")
|
||||
custom(parts(0), parts(1), encoding)
|
||||
require(parts.length == 2, s"`$value` is not a valid media-type. It must consist of two parts separated by '/'.")
|
||||
if (binary) customBinary(parts(0), parts(1), comp, fileExtensions)
|
||||
else customWithOpenCharset(parts(0), parts(1), fileExtensions)
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to parse a ``MediaType`` value from the given String. Returns ``Right(mediaType)`` if successful and
|
||||
* ``Left(errors)`` otherwise.
|
||||
* Tries to parse a ``MediaType`` value from the given String.
|
||||
* Returns ``Right(mediaType)`` if successful and ``Left(errors)`` otherwise.
|
||||
*/
|
||||
def parse(value: String): Either[List[ErrorInfo], MediaType] =
|
||||
ContentType.parse(value).right.map(_.mediaType)
|
||||
|
||||
def unapply(mediaType: MediaType): Option[String] = Some(mediaType.value)
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private def renderValue(mainType: String, subType: String, params: Map[String, String]): String = {
|
||||
val r = new StringRendering ~~ mainType ~~ '/' ~~ subType
|
||||
if (params.nonEmpty) params foreach { case (k, v) ⇒ r ~~ ';' ~~ ' ' ~~ k ~~ '=' ~~# v }
|
||||
r.get
|
||||
}
|
||||
|
||||
sealed abstract class Binary(val value: String, val mainType: String, val subType: String, val comp: Compressibility,
|
||||
val fileExtensions: List[String]) extends MediaType with jm.MediaType.Binary {
|
||||
def binary = true
|
||||
def params: Map[String, String] = Map.empty
|
||||
def withParams(params: Map[String, String]): Binary with MediaType =
|
||||
customBinary(mainType, subType, comp, fileExtensions, params)
|
||||
def withComp(comp: Compressibility): Binary with MediaType =
|
||||
customBinary(mainType, subType, comp, fileExtensions, params)
|
||||
|
||||
/**
|
||||
* JAVA API
|
||||
*/
|
||||
def toContentType: ContentType.Binary = ContentType(this)
|
||||
}
|
||||
|
||||
sealed abstract class NonBinary extends MediaType with jm.MediaType.NonBinary {
|
||||
def binary = false
|
||||
def comp = Compressible
|
||||
def withComp(comp: Compressibility): Binary with MediaType =
|
||||
customBinary(mainType, subType, comp, fileExtensions, params)
|
||||
}
|
||||
|
||||
sealed abstract class WithFixedCharset(val value: String, val mainType: String, val subType: String,
|
||||
val charset: HttpCharset, val fileExtensions: List[String])
|
||||
extends NonBinary with jm.MediaType.WithFixedCharset {
|
||||
def params: Map[String, String] = Map.empty
|
||||
def withParams(params: Map[String, String]): WithFixedCharset with MediaType =
|
||||
customWithFixedCharset(mainType, subType, charset, fileExtensions, params)
|
||||
|
||||
/**
|
||||
* JAVA API
|
||||
*/
|
||||
def toContentType: ContentType.WithFixedCharset = ContentType(this)
|
||||
}
|
||||
|
||||
sealed abstract class WithOpenCharset extends NonBinary with jm.MediaType.WithOpenCharset {
|
||||
def withCharset(charset: HttpCharset): ContentType.WithCharset = ContentType(this, charset)
|
||||
|
||||
/**
|
||||
* JAVA API
|
||||
*/
|
||||
def toContentType(charset: jm.HttpCharset): ContentType.WithCharset = withCharset(charset.asScala)
|
||||
}
|
||||
|
||||
sealed abstract class NonMultipartWithOpenCharset(val value: String, val mainType: String, val subType: String,
|
||||
val fileExtensions: List[String]) extends WithOpenCharset {
|
||||
def params: Map[String, String] = Map.empty
|
||||
def withParams(params: Map[String, String]): WithOpenCharset with MediaType =
|
||||
customWithOpenCharset(mainType, subType, fileExtensions, params)
|
||||
}
|
||||
|
||||
final class Multipart(val subType: String, val params: Map[String, String])
|
||||
extends WithOpenCharset with jm.MediaType.Multipart {
|
||||
val value = renderValue(mainType, subType, params)
|
||||
override def mainType = "multipart"
|
||||
override def isMultipart = true
|
||||
override def fileExtensions = Nil
|
||||
def withParams(params: Map[String, String]): MediaType.Multipart = new MediaType.Multipart(subType, params)
|
||||
def withBoundary(boundary: String): MediaType.Multipart =
|
||||
withParams(if (boundary.isEmpty) params - "boundary" else params.updated("boundary", boundary))
|
||||
}
|
||||
|
||||
sealed abstract class Compressibility(val compressible: Boolean)
|
||||
case object Compressible extends Compressibility(compressible = true)
|
||||
case object NotCompressible extends Compressibility(compressible = false)
|
||||
case object Gzipped extends Compressibility(compressible = false)
|
||||
}
|
||||
|
||||
object MediaTypes extends ObjectRegistry[(String, String), MediaType] {
|
||||
import MediaType.Encoding
|
||||
|
||||
private[this] var extensionMap = Map.empty[String, MediaType]
|
||||
|
||||
private def register(mediaType: MediaType): MediaType = {
|
||||
def registerFileExtension(ext: String): Unit = {
|
||||
val lcExt = ext.toRootLowerCase
|
||||
def forExtensionOption(ext: String): Option[MediaType] = extensionMap.get(ext.toLowerCase)
|
||||
def forExtension(ext: String): MediaType = extensionMap.getOrElse(ext.toLowerCase, `application/octet-stream`)
|
||||
|
||||
private def registerFileExtensions[T <: MediaType](mediaType: T): T = {
|
||||
mediaType.fileExtensions.foreach { ext ⇒
|
||||
val lcExt = ext.toLowerCase
|
||||
require(!extensionMap.contains(lcExt), s"Extension '$ext' clash: media-types '${extensionMap(lcExt)}' and '$mediaType'")
|
||||
extensionMap = extensionMap.updated(lcExt, mediaType)
|
||||
}
|
||||
mediaType.fileExtensions.foreach(registerFileExtension)
|
||||
mediaType
|
||||
}
|
||||
|
||||
private def register[T <: MediaType](mediaType: T): T = {
|
||||
registerFileExtensions(mediaType)
|
||||
register(mediaType.mainType.toRootLowerCase -> mediaType.subType.toRootLowerCase, mediaType)
|
||||
}
|
||||
|
||||
def forExtension(ext: String): Option[MediaType] = extensionMap.get(ext.toRootLowerCase)
|
||||
|
||||
private def app(subType: String, compressible: Boolean, encoding: Encoding, fileExtensions: String*) = register {
|
||||
new NonMultipartMediaType("application", subType, compressible, encoding, immutable.Seq(fileExtensions: _*)) {
|
||||
override def isApplication = true
|
||||
}
|
||||
}
|
||||
private def aud(subType: String, compressible: Boolean, fileExtensions: String*) = register {
|
||||
new NonMultipartMediaType("audio", subType, compressible, encoding = Encoding.Binary, immutable.Seq(fileExtensions: _*)) {
|
||||
override def isAudio = true
|
||||
}
|
||||
}
|
||||
private def img(subType: String, compressible: Boolean, encoding: Encoding, fileExtensions: String*) = register {
|
||||
new NonMultipartMediaType("image", subType, compressible, encoding, immutable.Seq(fileExtensions: _*)) {
|
||||
override def isImage = true
|
||||
}
|
||||
}
|
||||
private def msg(subType: String, fileExtensions: String*) = register {
|
||||
new NonMultipartMediaType("message", subType, compressible = true, encoding = Encoding.Binary,
|
||||
immutable.Seq(fileExtensions: _*)) {
|
||||
override def isMessage = true
|
||||
}
|
||||
}
|
||||
private def txt(subType: String, fileExtensions: String*) = register {
|
||||
new NonMultipartMediaType("text", subType, compressible = true, encoding = Encoding.Open, immutable.Seq(fileExtensions: _*)) {
|
||||
override def isText = true
|
||||
}
|
||||
}
|
||||
private def vid(subType: String, fileExtensions: String*) = register {
|
||||
new NonMultipartMediaType("video", subType, compressible = false, encoding = Encoding.Binary, immutable.Seq(fileExtensions: _*)) {
|
||||
override def isVideo = true
|
||||
}
|
||||
}
|
||||
|
||||
import MediaType._
|
||||
|
||||
/////////////////////////// PREDEFINED MEDIA-TYPE DEFINITION ////////////////////////////
|
||||
// format: OFF
|
||||
private final val compressible = true // compile-time constant
|
||||
private final val uncompressible = false // compile-time constant
|
||||
private def binary = Encoding.Binary
|
||||
private def openEncoding = Encoding.Open
|
||||
|
||||
private def abin(st: String, c: Compressibility, fe: String*) = register(applicationBinary(st, c, fe: _*))
|
||||
private def awfc(st: String, cs: HttpCharset, fe: String*) = register(applicationWithFixedCharset(st, cs, fe: _*))
|
||||
private def awoc(st: String, fe: String*) = register(applicationWithOpenCharset(st, fe: _*))
|
||||
private def aud(st: String, c: Compressibility, fe: String*) = register(audio(st, c, fe: _*))
|
||||
private def img(st: String, c: Compressibility, fe: String*) = register(image(st, c, fe: _*))
|
||||
private def msg(st: String, fe: String*) = register(message(st, Compressible, fe: _*))
|
||||
private def txt(st: String, fe: String*) = register(text(st, fe: _*))
|
||||
private def vid(st: String, fe: String*) = register(video(st, NotCompressible, fe: _*))
|
||||
|
||||
// dummy value currently only used by ContentType.NoContentType
|
||||
private[http] val NoMediaType = new NonMultipartMediaType("none", "none", false, Encoding.Binary, immutable.Seq.empty) {}
|
||||
private[http] val NoMediaType = MediaType.customBinary("none", "none", comp = NotCompressible)
|
||||
|
||||
val `application/atom+xml` = app("atom+xml", compressible, openEncoding, "atom")
|
||||
val `application/base64` = app("base64", compressible, binary, "mm", "mme")
|
||||
val `application/excel` = app("excel", uncompressible, binary, "xl", "xla", "xlb", "xlc", "xld", "xlk", "xll", "xlm", "xls", "xlt", "xlv", "xlw")
|
||||
val `application/font-woff` = app("font-woff", uncompressible, binary, "woff")
|
||||
val `application/gnutar` = app("gnutar", uncompressible, binary, "tgz")
|
||||
val `application/java-archive` = app("java-archive", uncompressible, binary, "jar", "war", "ear")
|
||||
val `application/javascript` = app("javascript", compressible, openEncoding, "js")
|
||||
val `application/json` = app("json", compressible, Encoding.Fixed(HttpCharsets.`UTF-8`), "json")
|
||||
val `application/json-patch+json` = app("json-patch+json", compressible, Encoding.Fixed(HttpCharsets.`UTF-8`))
|
||||
val `application/lha` = app("lha", uncompressible, binary, "lha")
|
||||
val `application/lzx` = app("lzx", uncompressible, binary, "lzx")
|
||||
val `application/mspowerpoint` = app("mspowerpoint", uncompressible, binary, "pot", "pps", "ppt", "ppz")
|
||||
val `application/msword` = app("msword", uncompressible, binary, "doc", "dot", "w6w", "wiz", "word", "wri")
|
||||
val `application/octet-stream` = app("octet-stream", uncompressible, binary, "a", "bin", "class", "dump", "exe", "lhx", "lzh", "o", "psd", "saveme", "zoo")
|
||||
val `application/pdf` = app("pdf", uncompressible, binary, "pdf")
|
||||
val `application/postscript` = app("postscript", compressible, binary, "ai", "eps", "ps")
|
||||
val `application/rss+xml` = app("rss+xml", compressible, openEncoding, "rss")
|
||||
val `application/soap+xml` = app("soap+xml", compressible, openEncoding)
|
||||
val `application/vnd.api+json` = app("vnd.api+json", compressible, Encoding.Fixed(HttpCharsets.`UTF-8`))
|
||||
val `application/vnd.google-earth.kml+xml` = app("vnd.google-earth.kml+xml", compressible, openEncoding, "kml")
|
||||
val `application/vnd.google-earth.kmz` = app("vnd.google-earth.kmz", uncompressible, binary, "kmz")
|
||||
val `application/vnd.ms-fontobject` = app("vnd.ms-fontobject", compressible, binary, "eot")
|
||||
val `application/vnd.oasis.opendocument.chart` = app("vnd.oasis.opendocument.chart", compressible, binary, "odc")
|
||||
val `application/vnd.oasis.opendocument.database` = app("vnd.oasis.opendocument.database", compressible, binary, "odb")
|
||||
val `application/vnd.oasis.opendocument.formula` = app("vnd.oasis.opendocument.formula", compressible, binary, "odf")
|
||||
val `application/vnd.oasis.opendocument.graphics` = app("vnd.oasis.opendocument.graphics", compressible, binary, "odg")
|
||||
val `application/vnd.oasis.opendocument.image` = app("vnd.oasis.opendocument.image", compressible, binary, "odi")
|
||||
val `application/vnd.oasis.opendocument.presentation` = app("vnd.oasis.opendocument.presentation", compressible, binary, "odp")
|
||||
val `application/vnd.oasis.opendocument.spreadsheet` = app("vnd.oasis.opendocument.spreadsheet", compressible, binary, "ods")
|
||||
val `application/vnd.oasis.opendocument.text` = app("vnd.oasis.opendocument.text", compressible, binary, "odt")
|
||||
val `application/vnd.oasis.opendocument.text-master` = app("vnd.oasis.opendocument.text-master", compressible, binary, "odm", "otm")
|
||||
val `application/vnd.oasis.opendocument.text-web` = app("vnd.oasis.opendocument.text-web", compressible, binary, "oth")
|
||||
val `application/vnd.openxmlformats-officedocument.presentationml.presentation` = app("vnd.openxmlformats-officedocument.presentationml.presentation", compressible, binary, "pptx")
|
||||
val `application/vnd.openxmlformats-officedocument.presentationml.slide` = app("vnd.openxmlformats-officedocument.presentationml.slide", compressible, binary, "sldx")
|
||||
val `application/vnd.openxmlformats-officedocument.presentationml.slideshow` = app("vnd.openxmlformats-officedocument.presentationml.slideshow", compressible, binary, "ppsx")
|
||||
val `application/vnd.openxmlformats-officedocument.presentationml.template` = app("vnd.openxmlformats-officedocument.presentationml.template", compressible, binary, "potx")
|
||||
val `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` = app("vnd.openxmlformats-officedocument.spreadsheetml.sheet", compressible, binary, "xlsx")
|
||||
val `application/vnd.openxmlformats-officedocument.spreadsheetml.template` = app("vnd.openxmlformats-officedocument.spreadsheetml.template", compressible, binary, "xltx")
|
||||
val `application/vnd.openxmlformats-officedocument.wordprocessingml.document` = app("vnd.openxmlformats-officedocument.wordprocessingml.document", compressible, binary, "docx")
|
||||
val `application/vnd.openxmlformats-officedocument.wordprocessingml.template` = app("vnd.openxmlformats-officedocument.wordprocessingml.template", compressible, binary, "dotx")
|
||||
val `application/x-7z-compressed` = app("x-7z-compressed", uncompressible, binary, "7z", "s7z")
|
||||
val `application/x-ace-compressed` = app("x-ace-compressed", uncompressible, binary, "ace")
|
||||
val `application/x-apple-diskimage` = app("x-apple-diskimage", uncompressible, binary, "dmg")
|
||||
val `application/x-arc-compressed` = app("x-arc-compressed", uncompressible, binary, "arc")
|
||||
val `application/x-bzip` = app("x-bzip", uncompressible, binary, "bz")
|
||||
val `application/x-bzip2` = app("x-bzip2", uncompressible, binary, "boz", "bz2")
|
||||
val `application/x-chrome-extension` = app("x-chrome-extension", uncompressible, binary, "crx")
|
||||
val `application/x-compress` = app("x-compress", uncompressible, binary, "z")
|
||||
val `application/x-compressed` = app("x-compressed", uncompressible, binary, "gz")
|
||||
val `application/x-debian-package` = app("x-debian-package", compressible, binary, "deb")
|
||||
val `application/x-dvi` = app("x-dvi", compressible, binary, "dvi")
|
||||
val `application/x-font-truetype` = app("x-font-truetype", compressible, binary, "ttf")
|
||||
val `application/x-font-opentype` = app("x-font-opentype", compressible, binary, "otf")
|
||||
val `application/x-gtar` = app("x-gtar", uncompressible, binary, "gtar")
|
||||
val `application/x-gzip` = app("x-gzip", uncompressible, binary, "gzip")
|
||||
val `application/x-latex` = app("x-latex", compressible, binary, "latex", "ltx")
|
||||
val `application/x-rar-compressed` = app("x-rar-compressed", uncompressible, binary, "rar")
|
||||
val `application/x-redhat-package-manager` = app("x-redhat-package-manager", uncompressible, binary, "rpm")
|
||||
val `application/x-shockwave-flash` = app("x-shockwave-flash", uncompressible, binary, "swf")
|
||||
val `application/x-tar` = app("x-tar", compressible, binary, "tar")
|
||||
val `application/x-tex` = app("x-tex", compressible, binary, "tex")
|
||||
val `application/x-texinfo` = app("x-texinfo", compressible, binary, "texi", "texinfo")
|
||||
val `application/x-vrml` = app("x-vrml", compressible, openEncoding, "vrml")
|
||||
val `application/x-www-form-urlencoded` = app("x-www-form-urlencoded", compressible, openEncoding)
|
||||
val `application/x-x509-ca-cert` = app("x-x509-ca-cert", compressible, binary, "der")
|
||||
val `application/x-xpinstall` = app("x-xpinstall", uncompressible, binary, "xpi")
|
||||
val `application/xhtml+xml` = app("xhtml+xml", compressible, openEncoding)
|
||||
val `application/xml-dtd` = app("xml-dtd", compressible, openEncoding)
|
||||
val `application/xml` = app("xml", compressible, openEncoding)
|
||||
val `application/zip` = app("zip", uncompressible, binary, "zip")
|
||||
val `application/atom+xml` = awoc("atom+xml", "atom")
|
||||
val `application/base64` = awoc("base64", "mm", "mme")
|
||||
val `application/excel` = abin("excel", NotCompressible, "xl", "xla", "xlb", "xlc", "xld", "xlk", "xll", "xlm", "xls", "xlt", "xlv", "xlw")
|
||||
val `application/font-woff` = abin("font-woff", NotCompressible, "woff")
|
||||
val `application/gnutar` = abin("gnutar", NotCompressible, "tgz")
|
||||
val `application/java-archive` = abin("java-archive", NotCompressible, "jar", "war", "ear")
|
||||
val `application/javascript` = awoc("javascript", "js")
|
||||
val `application/json` = awfc("json", HttpCharsets.`UTF-8`, "json")
|
||||
val `application/json-patch+json` = awfc("json-patch+json", HttpCharsets.`UTF-8`)
|
||||
val `application/lha` = abin("lha", NotCompressible, "lha")
|
||||
val `application/lzx` = abin("lzx", NotCompressible, "lzx")
|
||||
val `application/mspowerpoint` = abin("mspowerpoint", NotCompressible, "pot", "pps", "ppt", "ppz")
|
||||
val `application/msword` = abin("msword", NotCompressible, "doc", "dot", "w6w", "wiz", "word", "wri")
|
||||
val `application/octet-stream` = abin("octet-stream", NotCompressible, "a", "bin", "class", "dump", "exe", "lhx", "lzh", "o", "psd", "saveme", "zoo")
|
||||
val `application/pdf` = abin("pdf", NotCompressible, "pdf")
|
||||
val `application/postscript` = abin("postscript", Compressible, "ai", "eps", "ps")
|
||||
val `application/rss+xml` = awoc("rss+xml", "rss")
|
||||
val `application/soap+xml` = awoc("soap+xml")
|
||||
val `application/vnd.api+json` = awfc("vnd.api+json", HttpCharsets.`UTF-8`)
|
||||
val `application/vnd.google-earth.kml+xml` = awoc("vnd.google-earth.kml+xml", "kml")
|
||||
val `application/vnd.google-earth.kmz` = abin("vnd.google-earth.kmz", NotCompressible, "kmz")
|
||||
val `application/vnd.ms-fontobject` = abin("vnd.ms-fontobject", Compressible, "eot")
|
||||
val `application/vnd.oasis.opendocument.chart` = abin("vnd.oasis.opendocument.chart", Compressible, "odc")
|
||||
val `application/vnd.oasis.opendocument.database` = abin("vnd.oasis.opendocument.database", Compressible, "odb")
|
||||
val `application/vnd.oasis.opendocument.formula` = abin("vnd.oasis.opendocument.formula", Compressible, "odf")
|
||||
val `application/vnd.oasis.opendocument.graphics` = abin("vnd.oasis.opendocument.graphics", Compressible, "odg")
|
||||
val `application/vnd.oasis.opendocument.image` = abin("vnd.oasis.opendocument.image", Compressible, "odi")
|
||||
val `application/vnd.oasis.opendocument.presentation` = abin("vnd.oasis.opendocument.presentation", Compressible, "odp")
|
||||
val `application/vnd.oasis.opendocument.spreadsheet` = abin("vnd.oasis.opendocument.spreadsheet", Compressible, "ods")
|
||||
val `application/vnd.oasis.opendocument.text` = abin("vnd.oasis.opendocument.text", Compressible, "odt")
|
||||
val `application/vnd.oasis.opendocument.text-master` = abin("vnd.oasis.opendocument.text-master", Compressible, "odm", "otm")
|
||||
val `application/vnd.oasis.opendocument.text-web` = abin("vnd.oasis.opendocument.text-web", Compressible, "oth")
|
||||
val `application/vnd.openxmlformats-officedocument.presentationml.presentation` = abin("vnd.openxmlformats-officedocument.presentationml.presentation", Compressible, "pptx")
|
||||
val `application/vnd.openxmlformats-officedocument.presentationml.slide` = abin("vnd.openxmlformats-officedocument.presentationml.slide", Compressible, "sldx")
|
||||
val `application/vnd.openxmlformats-officedocument.presentationml.slideshow` = abin("vnd.openxmlformats-officedocument.presentationml.slideshow", Compressible, "ppsx")
|
||||
val `application/vnd.openxmlformats-officedocument.presentationml.template` = abin("vnd.openxmlformats-officedocument.presentationml.template", Compressible, "potx")
|
||||
val `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` = abin("vnd.openxmlformats-officedocument.spreadsheetml.sheet", Compressible, "xlsx")
|
||||
val `application/vnd.openxmlformats-officedocument.spreadsheetml.template` = abin("vnd.openxmlformats-officedocument.spreadsheetml.template", Compressible, "xltx")
|
||||
val `application/vnd.openxmlformats-officedocument.wordprocessingml.document` = abin("vnd.openxmlformats-officedocument.wordprocessingml.document", Compressible, "docx")
|
||||
val `application/vnd.openxmlformats-officedocument.wordprocessingml.template` = abin("vnd.openxmlformats-officedocument.wordprocessingml.template", Compressible, "dotx")
|
||||
val `application/x-7z-compressed` = abin("x-7z-compressed", NotCompressible, "7z", "s7z")
|
||||
val `application/x-ace-compressed` = abin("x-ace-compressed", NotCompressible, "ace")
|
||||
val `application/x-apple-diskimage` = abin("x-apple-diskimage", NotCompressible, "dmg")
|
||||
val `application/x-arc-compressed` = abin("x-arc-compressed", NotCompressible, "arc")
|
||||
val `application/x-bzip` = abin("x-bzip", NotCompressible, "bz")
|
||||
val `application/x-bzip2` = abin("x-bzip2", NotCompressible, "boz", "bz2")
|
||||
val `application/x-chrome-extension` = abin("x-chrome-extension", NotCompressible, "crx")
|
||||
val `application/x-compress` = abin("x-compress", NotCompressible, "z")
|
||||
val `application/x-compressed` = abin("x-compressed", NotCompressible, "gz")
|
||||
val `application/x-debian-package` = abin("x-debian-package", Compressible, "deb")
|
||||
val `application/x-dvi` = abin("x-dvi", Compressible, "dvi")
|
||||
val `application/x-font-truetype` = abin("x-font-truetype", Compressible, "ttf")
|
||||
val `application/x-font-opentype` = abin("x-font-opentype", Compressible, "otf")
|
||||
val `application/x-gtar` = abin("x-gtar", NotCompressible, "gtar")
|
||||
val `application/x-gzip` = abin("x-gzip", NotCompressible, "gzip")
|
||||
val `application/x-latex` = awoc("x-latex", "latex", "ltx")
|
||||
val `application/x-rar-compressed` = abin("x-rar-compressed", NotCompressible, "rar")
|
||||
val `application/x-redhat-package-manager` = abin("x-redhat-package-manager", NotCompressible, "rpm")
|
||||
val `application/x-shockwave-flash` = abin("x-shockwave-flash", NotCompressible, "swf")
|
||||
val `application/x-tar` = abin("x-tar", Compressible, "tar")
|
||||
val `application/x-tex` = abin("x-tex", Compressible, "tex")
|
||||
val `application/x-texinfo` = abin("x-texinfo", Compressible, "texi", "texinfo")
|
||||
val `application/x-vrml` = awoc("x-vrml", "vrml")
|
||||
val `application/x-www-form-urlencoded` = awoc("x-www-form-urlencoded")
|
||||
val `application/x-x509-ca-cert` = abin("x-x509-ca-cert", Compressible, "der")
|
||||
val `application/x-xpinstall` = abin("x-xpinstall", NotCompressible, "xpi")
|
||||
val `application/xhtml+xml` = awoc("xhtml+xml")
|
||||
val `application/xml-dtd` = awoc("xml-dtd")
|
||||
val `application/xml` = awoc("xml")
|
||||
val `application/zip` = abin("zip", NotCompressible, "zip")
|
||||
|
||||
val `audio/aiff` = aud("aiff", compressible, "aif", "aifc", "aiff")
|
||||
val `audio/basic` = aud("basic", compressible, "au", "snd")
|
||||
val `audio/midi` = aud("midi", compressible, "mid", "midi", "kar")
|
||||
val `audio/mod` = aud("mod", uncompressible, "mod")
|
||||
val `audio/mpeg` = aud("mpeg", uncompressible, "m2a", "mp2", "mp3", "mpa", "mpga")
|
||||
val `audio/ogg` = aud("ogg", uncompressible, "oga", "ogg")
|
||||
val `audio/voc` = aud("voc", uncompressible, "voc")
|
||||
val `audio/vorbis` = aud("vorbis", uncompressible, "vorbis")
|
||||
val `audio/voxware` = aud("voxware", uncompressible, "vox")
|
||||
val `audio/wav` = aud("wav", compressible, "wav")
|
||||
val `audio/x-realaudio` = aud("x-pn-realaudio", uncompressible, "ra", "ram", "rmm", "rmp")
|
||||
val `audio/x-psid` = aud("x-psid", compressible, "sid")
|
||||
val `audio/xm` = aud("xm", uncompressible, "xm")
|
||||
val `audio/webm` = aud("webm", uncompressible)
|
||||
val `audio/aiff` = aud("aiff", Compressible, "aif", "aifc", "aiff")
|
||||
val `audio/basic` = aud("basic", Compressible, "au", "snd")
|
||||
val `audio/midi` = aud("midi", Compressible, "mid", "midi", "kar")
|
||||
val `audio/mod` = aud("mod", NotCompressible, "mod")
|
||||
val `audio/mpeg` = aud("mpeg", NotCompressible, "m2a", "mp2", "mp3", "mpa", "mpga")
|
||||
val `audio/ogg` = aud("ogg", NotCompressible, "oga", "ogg")
|
||||
val `audio/voc` = aud("voc", NotCompressible, "voc")
|
||||
val `audio/vorbis` = aud("vorbis", NotCompressible, "vorbis")
|
||||
val `audio/voxware` = aud("voxware", NotCompressible, "vox")
|
||||
val `audio/wav` = aud("wav", Compressible, "wav")
|
||||
val `audio/x-realaudio` = aud("x-pn-realaudio", NotCompressible, "ra", "ram", "rmm", "rmp")
|
||||
val `audio/x-psid` = aud("x-psid", Compressible, "sid")
|
||||
val `audio/xm` = aud("xm", NotCompressible, "xm")
|
||||
val `audio/webm` = aud("webm", NotCompressible)
|
||||
|
||||
val `image/gif` = img("gif", uncompressible, binary, "gif")
|
||||
val `image/jpeg` = img("jpeg", uncompressible, binary, "jpe", "jpeg", "jpg")
|
||||
val `image/pict` = img("pict", compressible, binary, "pic", "pict")
|
||||
val `image/png` = img("png", uncompressible, binary, "png")
|
||||
val `image/svg+xml` = img("svg+xml", compressible, openEncoding, "svg")
|
||||
val `image/tiff` = img("tiff", compressible, binary, "tif", "tiff")
|
||||
val `image/x-icon` = img("x-icon", compressible, binary, "ico")
|
||||
val `image/x-ms-bmp` = img("x-ms-bmp", compressible, binary, "bmp")
|
||||
val `image/x-pcx` = img("x-pcx", compressible, binary, "pcx")
|
||||
val `image/x-pict` = img("x-pict", compressible, binary, "pct")
|
||||
val `image/x-quicktime` = img("x-quicktime", uncompressible, binary, "qif", "qti", "qtif")
|
||||
val `image/x-rgb` = img("x-rgb", compressible, binary, "rgb")
|
||||
val `image/x-xbitmap` = img("x-xbitmap", compressible, binary, "xbm")
|
||||
val `image/x-xpixmap` = img("x-xpixmap", compressible, binary, "xpm")
|
||||
val `image/webp` = img("webp", uncompressible, binary, "webp")
|
||||
val `image/gif` = img("gif", NotCompressible, "gif")
|
||||
val `image/jpeg` = img("jpeg", NotCompressible, "jpe", "jpeg", "jpg")
|
||||
val `image/pict` = img("pict", Compressible, "pic", "pict")
|
||||
val `image/png` = img("png", NotCompressible, "png")
|
||||
val `image/svg+xml` = img("svg+xml", Compressible, "svg")
|
||||
val `image/svgz` = registerFileExtensions(image("svg+xml", Gzipped, "svgz"))
|
||||
val `image/tiff` = img("tiff", Compressible, "tif", "tiff")
|
||||
val `image/x-icon` = img("x-icon", Compressible, "ico")
|
||||
val `image/x-ms-bmp` = img("x-ms-bmp", Compressible, "bmp")
|
||||
val `image/x-pcx` = img("x-pcx", Compressible, "pcx")
|
||||
val `image/x-pict` = img("x-pict", Compressible, "pct")
|
||||
val `image/x-quicktime` = img("x-quicktime", NotCompressible, "qif", "qti", "qtif")
|
||||
val `image/x-rgb` = img("x-rgb", Compressible, "rgb")
|
||||
val `image/x-xbitmap` = img("x-xbitmap", Compressible, "xbm")
|
||||
val `image/x-xpixmap` = img("x-xpixmap", Compressible, "xpm")
|
||||
val `image/webp` = img("webp", NotCompressible, "webp")
|
||||
|
||||
val `message/http` = msg("http")
|
||||
val `message/delivery-status` = msg("delivery-status")
|
||||
val `message/rfc822` = msg("rfc822", "eml", "mht", "mhtml", "mime")
|
||||
|
||||
object multipart {
|
||||
def apply(subType: String, params: Map[String, String]): MultipartMediaType = {
|
||||
require(subType != "*", "Cannot create a MediaRange here, use MediaRanges.`multipart/*` instead!")
|
||||
val r = new StringRendering ~~ "multipart/" ~~ subType
|
||||
if (params.nonEmpty) params foreach { case (k, v) ⇒ r ~~ ';' ~~ ' ' ~~ k ~~ '=' ~~# v }
|
||||
new MultipartMediaType(r.get, subType, params)
|
||||
}
|
||||
def mixed (params: Map[String, String]) = apply("mixed", params)
|
||||
def alternative(params: Map[String, String]) = apply("alternative", params)
|
||||
def related (params: Map[String, String]) = apply("related", params)
|
||||
def `form-data`(params: Map[String, String]) = apply("form-data", params)
|
||||
def signed (params: Map[String, String]) = apply("signed", params)
|
||||
def encrypted (params: Map[String, String]) = apply("encrypted", params)
|
||||
def byteRanges (params: Map[String, String]) = apply("byteranges", params)
|
||||
def mixed (params: Map[String, String]) = new MediaType.Multipart("mixed", params)
|
||||
def alternative(params: Map[String, String]) = new MediaType.Multipart("alternative", params)
|
||||
def related (params: Map[String, String]) = new MediaType.Multipart("related", params)
|
||||
def `form-data`(params: Map[String, String]) = new MediaType.Multipart("form-data", params)
|
||||
def signed (params: Map[String, String]) = new MediaType.Multipart("signed", params)
|
||||
def encrypted (params: Map[String, String]) = new MediaType.Multipart("encrypted", params)
|
||||
def byteRanges (params: Map[String, String]) = new MediaType.Multipart("byteranges", params)
|
||||
}
|
||||
|
||||
val `multipart/mixed` = multipart.mixed(Map.empty)
|
||||
|
|
@ -456,7 +422,7 @@ object MediaTypes extends ObjectRegistry[(String, String), MediaType] {
|
|||
|
||||
val `text/asp` = txt("asp", "asp")
|
||||
val `text/cache-manifest` = txt("cache-manifest", "manifest")
|
||||
val `text/calendar` = txt("calendar", "ics", "icz")
|
||||
val `text/calendar` = txt("calendar", "ics")
|
||||
val `text/css` = txt("css", "css")
|
||||
val `text/csv` = txt("csv", "csv")
|
||||
val `text/html` = txt("html", "htm", "html", "htmls", "htx")
|
||||
|
|
|
|||
|
|
@ -15,14 +15,14 @@ import scala.concurrent.{ Future, ExecutionContext }
|
|||
import scala.collection.immutable
|
||||
import scala.util.{ Failure, Success, Try }
|
||||
import akka.stream.Materializer
|
||||
import akka.stream.scaladsl.{ Source }
|
||||
import akka.stream.scaladsl.Source
|
||||
import akka.http.scaladsl.util.FastFuture
|
||||
import akka.http.scaladsl.model.headers._
|
||||
import akka.http.impl.engine.rendering.BodyPartRenderer
|
||||
import FastFuture._
|
||||
|
||||
sealed trait Multipart {
|
||||
def mediaType: MultipartMediaType
|
||||
def mediaType: MediaType.Multipart
|
||||
def parts: Source[Multipart.BodyPart, Any]
|
||||
|
||||
/**
|
||||
|
|
@ -41,7 +41,7 @@ sealed trait Multipart {
|
|||
parts
|
||||
.transform(() ⇒ BodyPartRenderer.streamed(boundary, charset.nioCharset, partHeadersSizeHint = 128, log))
|
||||
.flatMapConcat(ConstantFun.scalaIdentityFunction)
|
||||
HttpEntity.Chunked(mediaType withBoundary boundary, chunks)
|
||||
HttpEntity.Chunked(mediaType withBoundary boundary withCharset charset, chunks)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -52,7 +52,7 @@ object Multipart {
|
|||
|
||||
override def toEntity(charset: HttpCharset, boundary: String)(implicit log: LoggingAdapter = NoLogging): HttpEntity.Strict = {
|
||||
val data = BodyPartRenderer.strict(strictParts, boundary, charset.nioCharset, partHeadersSizeHint = 128, log)
|
||||
HttpEntity(mediaType withBoundary boundary, data)
|
||||
HttpEntity(mediaType withBoundary boundary withCharset charset, data)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -91,27 +91,27 @@ object Multipart {
|
|||
* Basic model for multipart content as defined by http://tools.ietf.org/html/rfc2046.
|
||||
*/
|
||||
sealed abstract class General extends Multipart {
|
||||
def mediaType: MultipartMediaType
|
||||
def mediaType: MediaType.Multipart
|
||||
def parts: Source[General.BodyPart, Any]
|
||||
def toStrict(timeout: FiniteDuration)(implicit ec: ExecutionContext, fm: Materializer): Future[General.Strict] =
|
||||
strictify(parts)(_.toStrict(timeout)).fast.map(General.Strict(mediaType, _))
|
||||
}
|
||||
object General {
|
||||
def apply(mediaType: MultipartMediaType, parts: BodyPart.Strict*): Strict = Strict(mediaType, parts.toVector)
|
||||
def apply(mediaType: MediaType.Multipart, parts: BodyPart.Strict*): Strict = Strict(mediaType, parts.toVector)
|
||||
|
||||
def apply(_mediaType: MultipartMediaType, _parts: Source[BodyPart, Any]): General =
|
||||
def apply(_mediaType: MediaType.Multipart, _parts: Source[BodyPart, Any]): General =
|
||||
new General {
|
||||
def mediaType = _mediaType
|
||||
def parts = _parts
|
||||
override def toString = s"General($mediaType, $parts)"
|
||||
}
|
||||
|
||||
def unapply(value: General): Option[(MultipartMediaType, Source[BodyPart, Any])] = Some(value.mediaType -> value.parts)
|
||||
def unapply(value: General): Option[(MediaType.Multipart, Source[BodyPart, Any])] = Some(value.mediaType -> value.parts)
|
||||
|
||||
/**
|
||||
* Strict [[General]].
|
||||
*/
|
||||
case class Strict(mediaType: MultipartMediaType, strictParts: immutable.Seq[BodyPart.Strict]) extends General with Multipart.Strict {
|
||||
case class Strict(mediaType: MediaType.Multipart, strictParts: immutable.Seq[BodyPart.Strict]) extends General with Multipart.Strict {
|
||||
def parts: Source[BodyPart.Strict, Any] = Source(strictParts)
|
||||
override def toStrict(timeout: FiniteDuration)(implicit ec: ExecutionContext, fm: Materializer) =
|
||||
FastFuture.successful(this)
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ object HttpEncodingRange {
|
|||
case class `*`(qValue: Float) extends HttpEncodingRange {
|
||||
require(0.0f <= qValue && qValue <= 1.0f, "qValue must be >= 0 and <= 1.0")
|
||||
final def render[R <: Rendering](r: R): r.type = if (qValue < 1.0f) r ~~ "*;q=" ~~ qValue else r ~~ '*'
|
||||
def matches(encoding: HttpEncoding) = qValue > 0f
|
||||
def matches(encoding: HttpEncoding) = true
|
||||
def withQValue(qValue: Float) =
|
||||
if (qValue == 1.0f) `*` else if (qValue != this.qValue) `*`(qValue.toFloat) else this
|
||||
}
|
||||
|
|
@ -30,7 +30,7 @@ object HttpEncodingRange {
|
|||
|
||||
final case class One(encoding: HttpEncoding, qValue: Float) extends HttpEncodingRange {
|
||||
require(0.0f <= qValue && qValue <= 1.0f, "qValue must be >= 0 and <= 1.0")
|
||||
def matches(encoding: HttpEncoding) = qValue > 0f && this.encoding.value.equalsIgnoreCase(encoding.value)
|
||||
def matches(encoding: HttpEncoding) = this.encoding.value.equalsIgnoreCase(encoding.value)
|
||||
def withQValue(qValue: Float) = One(encoding, qValue)
|
||||
def render[R <: Rendering](r: R): r.type = if (qValue < 1.0f) r ~~ encoding ~~ ";q=" ~~ qValue else r ~~ encoding
|
||||
}
|
||||
|
|
@ -55,7 +55,6 @@ object HttpEncodings extends ObjectRegistry[String, HttpEncoding] {
|
|||
val deflate = register("deflate")
|
||||
val gzip = register("gzip")
|
||||
val identity = register("identity")
|
||||
val `identity;q=MIN` = identity.withQValue(Float.MinPositiveValue)
|
||||
val `x-compress` = register("x-compress")
|
||||
val `x-zip` = register("x-zip")
|
||||
// format: ON
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ class RequestParserSpec extends FreeSpec with Matchers with BeforeAndAfterAll {
|
|||
|Content-Type: application/pdf
|
||||
|Content-Length: 0
|
||||
|
|
||||
|""" should parseTo(HttpRequest(GET, "/data", List(Host("x")), HttpEntity(`application/pdf`, "")))
|
||||
|""" should parseTo(HttpRequest(GET, "/data", List(Host("x")), HttpEntity.empty(`application/pdf`)))
|
||||
closeAfterResponseCompletion shouldEqual Seq(false)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import org.scalatest.{ FreeSpec, Matchers, BeforeAndAfterAll }
|
|||
import org.scalatest.matchers.Matcher
|
||||
import akka.actor.ActorSystem
|
||||
import akka.event.NoLogging
|
||||
import akka.util.ByteString
|
||||
import akka.http.scaladsl.model._
|
||||
import akka.http.scaladsl.model.headers._
|
||||
import akka.http.impl.util._
|
||||
|
|
@ -99,7 +100,7 @@ class RequestRendererSpec extends FreeSpec with Matchers with BeforeAndAfterAll
|
|||
HttpRequest(PUT, "/abc/xyz", List(
|
||||
RawHeader("X-Fancy", "naa"),
|
||||
RawHeader("Cache-Control", "public"),
|
||||
Host("spray.io")), HttpEntity(ContentTypes.NoContentType, "The content please!")) should renderTo {
|
||||
Host("spray.io")), HttpEntity(ContentTypes.NoContentType, ByteString("The content please!"))) should renderTo {
|
||||
"""PUT /abc/xyz HTTP/1.1
|
||||
|X-Fancy: naa
|
||||
|Cache-Control: public
|
||||
|
|
@ -140,11 +141,11 @@ class RequestRendererSpec extends FreeSpec with Matchers with BeforeAndAfterAll
|
|||
|
||||
"PUT request with empty chunk stream and custom Content-Type" in new TestSetup() {
|
||||
pending // Disabled until #15981 is fixed
|
||||
HttpRequest(PUT, "/abc/xyz", entity = Chunked(ContentTypes.`text/plain`, source())) should renderTo {
|
||||
HttpRequest(PUT, "/abc/xyz", entity = Chunked(ContentTypes.`text/plain(UTF-8)`, source())) should renderTo {
|
||||
"""PUT /abc/xyz HTTP/1.1
|
||||
|Host: test.com:8080
|
||||
|User-Agent: akka-http/1.0.0
|
||||
|Content-Type: text/plain
|
||||
|Content-Type: text/plain; charset=UTF-8
|
||||
|Content-Length: 0
|
||||
|
|
||||
|"""
|
||||
|
|
@ -152,13 +153,13 @@ class RequestRendererSpec extends FreeSpec with Matchers with BeforeAndAfterAll
|
|||
}
|
||||
|
||||
"POST request with body" in new TestSetup() {
|
||||
HttpRequest(POST, "/abc/xyz", entity = Chunked(ContentTypes.`text/plain`,
|
||||
HttpRequest(POST, "/abc/xyz", entity = Chunked(ContentTypes.`text/plain(UTF-8)`,
|
||||
source("XXXX", "ABCDEFGHIJKLMNOPQRSTUVWXYZ"))) should renderTo {
|
||||
"""POST /abc/xyz HTTP/1.1
|
||||
|Host: test.com:8080
|
||||
|User-Agent: akka-http/1.0.0
|
||||
|Transfer-Encoding: chunked
|
||||
|Content-Type: text/plain
|
||||
|Content-Type: text/plain; charset=UTF-8
|
||||
|
|
||||
|4
|
||||
|XXXX
|
||||
|
|
@ -177,13 +178,13 @@ class RequestRendererSpec extends FreeSpec with Matchers with BeforeAndAfterAll
|
|||
ChunkStreamPart("ABCDEFGHIJKLMNOPQRSTUVWXYZ"),
|
||||
LastChunk)
|
||||
|
||||
HttpRequest(POST, "/abc/xyz", entity = Chunked(ContentTypes.`text/plain`,
|
||||
HttpRequest(POST, "/abc/xyz", entity = Chunked(ContentTypes.`text/plain(UTF-8)`,
|
||||
Source(chunks))) should renderTo {
|
||||
"""POST /abc/xyz HTTP/1.1
|
||||
|Host: test.com:8080
|
||||
|User-Agent: akka-http/1.0.0
|
||||
|Transfer-Encoding: chunked
|
||||
|Content-Type: text/plain
|
||||
|Content-Type: text/plain; charset=UTF-8
|
||||
|
|
||||
|4
|
||||
|XXXX
|
||||
|
|
@ -203,13 +204,13 @@ class RequestRendererSpec extends FreeSpec with Matchers with BeforeAndAfterAll
|
|||
LastChunk,
|
||||
LastChunk)
|
||||
|
||||
HttpRequest(POST, "/abc/xyz", entity = Chunked(ContentTypes.`text/plain`,
|
||||
HttpRequest(POST, "/abc/xyz", entity = Chunked(ContentTypes.`text/plain(UTF-8)`,
|
||||
Source(chunks))) should renderTo {
|
||||
"""POST /abc/xyz HTTP/1.1
|
||||
|Host: test.com:8080
|
||||
|User-Agent: akka-http/1.0.0
|
||||
|Transfer-Encoding: chunked
|
||||
|Content-Type: text/plain
|
||||
|Content-Type: text/plain; charset=UTF-8
|
||||
|
|
||||
|4
|
||||
|XXXX
|
||||
|
|
@ -223,12 +224,12 @@ class RequestRendererSpec extends FreeSpec with Matchers with BeforeAndAfterAll
|
|||
|
||||
"POST request with custom Transfer-Encoding header" in new TestSetup() {
|
||||
HttpRequest(POST, "/abc/xyz", List(`Transfer-Encoding`(TransferEncodings.Extension("fancy"))),
|
||||
entity = Chunked(ContentTypes.`text/plain`, source("XXXX", "ABCDEFGHIJKLMNOPQRSTUVWXYZ"))) should renderTo {
|
||||
entity = Chunked(ContentTypes.`text/plain(UTF-8)`, source("XXXX", "ABCDEFGHIJKLMNOPQRSTUVWXYZ"))) should renderTo {
|
||||
"""POST /abc/xyz HTTP/1.1
|
||||
|Transfer-Encoding: fancy, chunked
|
||||
|Host: test.com:8080
|
||||
|User-Agent: akka-http/1.0.0
|
||||
|Content-Type: text/plain
|
||||
|Content-Type: text/plain; charset=UTF-8
|
||||
|
|
||||
|4
|
||||
|XXXX
|
||||
|
|
|
|||
|
|
@ -174,7 +174,7 @@ class ResponseRendererSpec extends FreeSpec with Matchers with BeforeAndAfterAll
|
|||
|
||||
"status 400, a few headers and a body with an explicitly suppressed Content Type header" in new TestSetup() {
|
||||
HttpResponse(400, List(Age(30), Connection("Keep-Alive")),
|
||||
HttpEntity(contentType = ContentTypes.NoContentType, "Small f*ck up overhere!")) should renderTo {
|
||||
HttpEntity(ContentTypes.NoContentType, ByteString("Small f*ck up overhere!"))) should renderTo {
|
||||
"""HTTP/1.1 400 Bad Request
|
||||
|Age: 30
|
||||
|Server: akka-http/1.0.0
|
||||
|
|
@ -202,7 +202,7 @@ class ResponseRendererSpec extends FreeSpec with Matchers with BeforeAndAfterAll
|
|||
"a response with a Default (streamed with explicit content-length body," - {
|
||||
"status 400 and a few headers" in new TestSetup() {
|
||||
HttpResponse(400, List(Age(30), Connection("Keep-Alive")),
|
||||
entity = Default(contentType = ContentTypes.`text/plain(UTF-8)`, 23, source(ByteString("Small f*ck up overhere!")))) should renderTo {
|
||||
entity = Default(ContentTypes.`text/plain(UTF-8)`, 23, source(ByteString("Small f*ck up overhere!")))) should renderTo {
|
||||
"""HTTP/1.1 400 Bad Request
|
||||
|Age: 30
|
||||
|Server: akka-http/1.0.0
|
||||
|
|
|
|||
|
|
@ -385,12 +385,12 @@ class HttpServerSpec extends AkkaSpec("akka.loggers = []\n akka.loglevel = OFF")
|
|||
|""")
|
||||
inside(expectRequest()) {
|
||||
case HttpRequest(GET, _, _, _, _) ⇒
|
||||
responses.sendNext(HttpResponse(entity = HttpEntity.Strict(ContentTypes.`text/plain`, ByteString("abcd"))))
|
||||
responses.sendNext(HttpResponse(entity = HttpEntity.Strict(ContentTypes.`text/plain(UTF-8)`, ByteString("abcd"))))
|
||||
expectResponseWithWipedDate(
|
||||
"""|HTTP/1.1 200 OK
|
||||
|Server: akka-http/test
|
||||
|Date: XXXX
|
||||
|Content-Type: text/plain
|
||||
|Content-Type: text/plain; charset=UTF-8
|
||||
|Content-Length: 4
|
||||
|
|
||||
|""")
|
||||
|
|
@ -405,14 +405,14 @@ class HttpServerSpec extends AkkaSpec("akka.loggers = []\n akka.loglevel = OFF")
|
|||
val data = TestPublisher.manualProbe[ByteString]()
|
||||
inside(expectRequest()) {
|
||||
case HttpRequest(GET, _, _, _, _) ⇒
|
||||
responses.sendNext(HttpResponse(entity = HttpEntity.Default(ContentTypes.`text/plain`, 4, Source(data))))
|
||||
responses.sendNext(HttpResponse(entity = HttpEntity.Default(ContentTypes.`text/plain(UTF-8)`, 4, Source(data))))
|
||||
val dataSub = data.expectSubscription()
|
||||
dataSub.expectCancellation()
|
||||
expectResponseWithWipedDate(
|
||||
"""|HTTP/1.1 200 OK
|
||||
|Server: akka-http/test
|
||||
|Date: XXXX
|
||||
|Content-Type: text/plain
|
||||
|Content-Type: text/plain; charset=UTF-8
|
||||
|Content-Length: 4
|
||||
|
|
||||
|""")
|
||||
|
|
@ -427,14 +427,14 @@ class HttpServerSpec extends AkkaSpec("akka.loggers = []\n akka.loglevel = OFF")
|
|||
val data = TestPublisher.manualProbe[ByteString]()
|
||||
inside(expectRequest()) {
|
||||
case HttpRequest(GET, _, _, _, _) ⇒
|
||||
responses.sendNext(HttpResponse(entity = HttpEntity.CloseDelimited(ContentTypes.`text/plain`, Source(data))))
|
||||
responses.sendNext(HttpResponse(entity = HttpEntity.CloseDelimited(ContentTypes.`text/plain(UTF-8)`, Source(data))))
|
||||
val dataSub = data.expectSubscription()
|
||||
dataSub.expectCancellation()
|
||||
expectResponseWithWipedDate(
|
||||
"""|HTTP/1.1 200 OK
|
||||
|Server: akka-http/test
|
||||
|Date: XXXX
|
||||
|Content-Type: text/plain
|
||||
|Content-Type: text/plain; charset=UTF-8
|
||||
|
|
||||
|""")
|
||||
}
|
||||
|
|
@ -450,7 +450,7 @@ class HttpServerSpec extends AkkaSpec("akka.loggers = []\n akka.loglevel = OFF")
|
|||
val data = TestPublisher.manualProbe[ChunkStreamPart]()
|
||||
inside(expectRequest()) {
|
||||
case HttpRequest(GET, _, _, _, _) ⇒
|
||||
responses.sendNext(HttpResponse(entity = HttpEntity.Chunked(ContentTypes.`text/plain`, Source(data))))
|
||||
responses.sendNext(HttpResponse(entity = HttpEntity.Chunked(ContentTypes.`text/plain(UTF-8)`, Source(data))))
|
||||
val dataSub = data.expectSubscription()
|
||||
dataSub.expectCancellation()
|
||||
expectResponseWithWipedDate(
|
||||
|
|
@ -458,7 +458,7 @@ class HttpServerSpec extends AkkaSpec("akka.loggers = []\n akka.loglevel = OFF")
|
|||
|Server: akka-http/test
|
||||
|Date: XXXX
|
||||
|Transfer-Encoding: chunked
|
||||
|Content-Type: text/plain
|
||||
|Content-Type: text/plain; charset=UTF-8
|
||||
|
|
||||
|""")
|
||||
}
|
||||
|
|
@ -473,7 +473,7 @@ class HttpServerSpec extends AkkaSpec("akka.loggers = []\n akka.loglevel = OFF")
|
|||
val data = TestPublisher.manualProbe[ByteString]()
|
||||
inside(expectRequest()) {
|
||||
case HttpRequest(GET, _, _, _, _) ⇒
|
||||
responses.sendNext(HttpResponse(entity = CloseDelimited(ContentTypes.`text/plain`, Source(data))))
|
||||
responses.sendNext(HttpResponse(entity = CloseDelimited(ContentTypes.`text/plain(UTF-8)`, Source(data))))
|
||||
val dataSub = data.expectSubscription()
|
||||
dataSub.expectCancellation()
|
||||
netOut.expectBytes(1)
|
||||
|
|
@ -884,6 +884,7 @@ class HttpServerSpec extends AkkaSpec("akka.loggers = []\n akka.loglevel = OFF")
|
|||
"the config setting applied before another attribute (default entity)" in new LengthVerificationTest(maxContentLength = 10) {
|
||||
def nameDataSource(name: String): RequestEntity ⇒ RequestEntity = {
|
||||
case x: HttpEntity.Default ⇒ x.copy(data = x.data named name)
|
||||
case _ ⇒ ??? // prevent a compile-time warning
|
||||
}
|
||||
sendDefaultRequestWithLength(10)
|
||||
expectRequest().mapEntity(nameDataSource("foo")).expectEntity[HttpEntity.Default](10)
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import HttpEncodings._
|
|||
import HttpMethods._
|
||||
|
||||
class HttpHeaderSpec extends FreeSpec with Matchers {
|
||||
val `application/vnd.spray` = MediaType.custom("application/vnd.spray", MediaType.Encoding.Binary)
|
||||
val `application/vnd.spray` = MediaType.applicationBinary("vnd.spray", MediaType.Compressible)
|
||||
val PROPFIND = HttpMethod.custom("PROPFIND")
|
||||
|
||||
"The HTTP header model must correctly parse and render the headers" - {
|
||||
|
|
@ -38,9 +38,9 @@ class HttpHeaderSpec extends FreeSpec with Matchers {
|
|||
"Accept: */*, text/*; foo=bar, custom/custom; bar=\"b>az\"" =!=
|
||||
Accept(`*/*`,
|
||||
MediaRange.custom("text", Map("foo" -> "bar")),
|
||||
MediaType.custom("custom", "custom", MediaType.Encoding.Binary, params = Map("bar" -> "b>az")))
|
||||
MediaType.customBinary("custom", "custom", MediaType.Compressible, params = Map("bar" -> "b>az")))
|
||||
"Accept: application/*+xml; version=2" =!=
|
||||
Accept(MediaType.custom("application", "*+xml", MediaType.Encoding.Binary, params = Map("version" -> "2")))
|
||||
Accept(MediaType.customBinary("application", "*+xml", MediaType.Compressible, params = Map("version" -> "2")))
|
||||
}
|
||||
|
||||
"Accept-Charset" in {
|
||||
|
|
@ -197,17 +197,18 @@ class HttpHeaderSpec extends FreeSpec with Matchers {
|
|||
"Content-Type: text/plain; charset=utf8" =!=
|
||||
`Content-Type`(ContentType(`text/plain`, `UTF-8`)).renderedTo("text/plain; charset=UTF-8")
|
||||
"Content-Type: text/xml2; version=3; charset=windows-1252" =!=
|
||||
`Content-Type`(ContentType(MediaType.custom("text", "xml2", encoding = MediaType.Encoding.Open,
|
||||
params = Map("version" -> "3")), HttpCharsets.getForKey("windows-1252")))
|
||||
`Content-Type`(MediaType.customWithOpenCharset("text", "xml2", params = Map("version" -> "3"))
|
||||
withCharset HttpCharsets.getForKey("windows-1252").get)
|
||||
"Content-Type: text/plain; charset=fancy-pants" =!=
|
||||
`Content-Type`(ContentType(`text/plain`, HttpCharset.custom("fancy-pants")))
|
||||
`Content-Type`(`text/plain` withCharset HttpCharset.custom("fancy-pants"))
|
||||
"Content-Type: multipart/mixed; boundary=ABC123" =!=
|
||||
`Content-Type`(ContentType(`multipart/mixed` withBoundary "ABC123"))
|
||||
`Content-Type`(`multipart/mixed` withBoundary "ABC123" withCharset `UTF-8`)
|
||||
.renderedTo("multipart/mixed; boundary=ABC123; charset=UTF-8")
|
||||
"Content-Type: multipart/mixed; boundary=\"ABC/123\"" =!=
|
||||
`Content-Type`(ContentType(`multipart/mixed` withBoundary "ABC/123"))
|
||||
`Content-Type`(`multipart/mixed` withBoundary "ABC/123" withCharset `UTF-8`)
|
||||
.renderedTo("""multipart/mixed; boundary="ABC/123"; charset=UTF-8""")
|
||||
"Content-Type: application/*" =!=
|
||||
`Content-Type`(ContentType(MediaType.custom("application", "*", MediaType.Encoding.Binary,
|
||||
allowArbitrarySubtypes = true)))
|
||||
`Content-Type`(MediaType.customBinary("application", "*", MediaType.Compressible, allowArbitrarySubtypes = true))
|
||||
}
|
||||
|
||||
"Content-Range" in {
|
||||
|
|
|
|||
|
|
@ -164,7 +164,7 @@ class ClientServerSpec extends WordSpec with Matchers with BeforeAndAfterAll {
|
|||
|
||||
try {
|
||||
def runIdleRequest(uri: Uri): Future[HttpResponse] = {
|
||||
val itNeverEnds = Chunked.fromData(ContentTypes.`text/plain`, Source.maybe[ByteString])
|
||||
val itNeverEnds = Chunked.fromData(ContentTypes.`text/plain(UTF-8)`, Source.maybe[ByteString])
|
||||
Http().outgoingConnection(hostname, port)
|
||||
.runWith(Source.single(HttpRequest(PUT, uri, entity = itNeverEnds)), Sink.head)
|
||||
._2
|
||||
|
|
@ -197,7 +197,7 @@ class ClientServerSpec extends WordSpec with Matchers with BeforeAndAfterAll {
|
|||
|
||||
try {
|
||||
def runRequest(uri: Uri): Future[HttpResponse] = {
|
||||
val itNeverSends = Chunked.fromData(ContentTypes.`text/plain`, Source.maybe[ByteString])
|
||||
val itNeverSends = Chunked.fromData(ContentTypes.`text/plain(UTF-8)`, Source.maybe[ByteString])
|
||||
Http().outgoingConnection(hostname, port, settings = clientSettings)
|
||||
.runWith(Source.single(HttpRequest(POST, uri, entity = itNeverSends)), Sink.head)
|
||||
._2
|
||||
|
|
@ -232,7 +232,7 @@ class ClientServerSpec extends WordSpec with Matchers with BeforeAndAfterAll {
|
|||
val pool = Http().cachedHostConnectionPool[Int](hostname, port, clientPoolSettings)
|
||||
|
||||
def runRequest(uri: Uri): Future[(Try[HttpResponse], Int)] = {
|
||||
val itNeverSends = Chunked.fromData(ContentTypes.`text/plain`, Source.maybe[ByteString])
|
||||
val itNeverSends = Chunked.fromData(ContentTypes.`text/plain(UTF-8)`, Source.maybe[ByteString])
|
||||
Source.single(HttpRequest(POST, uri, entity = itNeverSends) -> 1)
|
||||
.via(pool)
|
||||
.runWith(Sink.head)
|
||||
|
|
@ -265,7 +265,7 @@ class ClientServerSpec extends WordSpec with Matchers with BeforeAndAfterAll {
|
|||
|
||||
try {
|
||||
def runRequest(uri: Uri): Future[HttpResponse] = {
|
||||
val itNeverSends = Chunked.fromData(ContentTypes.`text/plain`, Source.maybe[ByteString])
|
||||
val itNeverSends = Chunked.fromData(ContentTypes.`text/plain(UTF-8)`, Source.maybe[ByteString])
|
||||
Http().singleRequest(HttpRequest(POST, uri, entity = itNeverSends), settings = clientPoolSettings)
|
||||
}
|
||||
|
||||
|
|
@ -368,7 +368,7 @@ class ClientServerSpec extends WordSpec with Matchers with BeforeAndAfterAll {
|
|||
val (serverIn, serverOut) = acceptConnection()
|
||||
|
||||
val chunks = List(Chunk("abc"), Chunk("defg"), Chunk("hijkl"), LastChunk)
|
||||
val chunkedContentType: ContentType = MediaTypes.`application/base64`
|
||||
val chunkedContentType: ContentType = MediaTypes.`application/base64` withCharset HttpCharsets.`UTF-8`
|
||||
val chunkedEntity = HttpEntity.Chunked(chunkedContentType, Source(chunks))
|
||||
|
||||
val clientOutSub = clientOut.expectSubscription()
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ object TestServer extends App {
|
|||
////////////// helpers //////////////
|
||||
|
||||
lazy val index = HttpResponse(
|
||||
entity = HttpEntity(MediaTypes.`text/html`,
|
||||
entity = HttpEntity(ContentTypes.`text/html(UTF-8)`,
|
||||
"""|<html>
|
||||
| <body>
|
||||
| <h1>Say hello to <i>akka-http-core</i>!</h1>
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ class HttpModelIntegrationSpec extends WordSpec with Matchers with BeforeAndAfte
|
|||
val contentType = convertedHeaders.collectFirst {
|
||||
case ct: `Content-Type` ⇒ ct.contentType
|
||||
}
|
||||
contentType shouldEqual Some(ContentTypes.`text/plain`)
|
||||
contentType shouldEqual Some(ContentTypes.`text/plain(UTF-8)`)
|
||||
|
||||
val contentLength = convertedHeaders.collectFirst {
|
||||
case cl: `Content-Length` ⇒ cl.length
|
||||
|
|
@ -207,7 +207,7 @@ class HttpModelIntegrationSpec extends WordSpec with Matchers with BeforeAndAfte
|
|||
// to be able to directly create a ContentType and ContentLength
|
||||
// headers.
|
||||
ExampleLibrary.contentLength(3)
|
||||
ExampleLibrary.contentType(ContentTypes.`text/plain`)
|
||||
ExampleLibrary.contentType(ContentTypes.`text/plain(UTF-8)`)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,10 +5,9 @@
|
|||
package akka.http.scaladsl.marshallers.sprayjson
|
||||
|
||||
import scala.language.implicitConversions
|
||||
import akka.stream.Materializer
|
||||
import akka.http.scaladsl.marshalling.{ ToEntityMarshaller, Marshaller }
|
||||
import akka.http.scaladsl.unmarshalling.{ FromEntityUnmarshaller, Unmarshaller }
|
||||
import akka.http.scaladsl.model.{ ContentTypes, HttpCharsets }
|
||||
import akka.http.scaladsl.model.{ MediaTypes, HttpCharsets }
|
||||
import akka.http.scaladsl.model.MediaTypes.`application/json`
|
||||
import spray.json._
|
||||
|
||||
|
|
@ -33,6 +32,6 @@ trait SprayJsonSupport {
|
|||
implicit def sprayJsonMarshaller[T](implicit writer: RootJsonWriter[T], printer: JsonPrinter = PrettyPrinter): ToEntityMarshaller[T] =
|
||||
sprayJsValueMarshaller compose writer.write
|
||||
implicit def sprayJsValueMarshaller(implicit printer: JsonPrinter = PrettyPrinter): ToEntityMarshaller[JsValue] =
|
||||
Marshaller.StringMarshaller.wrap(ContentTypes.`application/json`)(printer)
|
||||
Marshaller.StringMarshaller.wrap(MediaTypes.`application/json`)(printer)
|
||||
}
|
||||
object SprayJsonSupport extends SprayJsonSupport
|
||||
|
|
@ -8,7 +8,6 @@ import java.io.{ ByteArrayInputStream, InputStreamReader }
|
|||
import javax.xml.parsers.{ SAXParserFactory, SAXParser }
|
||||
import scala.collection.immutable
|
||||
import scala.xml.{ XML, NodeSeq }
|
||||
import akka.stream.Materializer
|
||||
import akka.http.scaladsl.unmarshalling._
|
||||
import akka.http.scaladsl.marshalling._
|
||||
import akka.http.scaladsl.model._
|
||||
|
|
@ -16,10 +15,10 @@ import MediaTypes._
|
|||
|
||||
trait ScalaXmlSupport {
|
||||
implicit def defaultNodeSeqMarshaller: ToEntityMarshaller[NodeSeq] =
|
||||
Marshaller.oneOf(ScalaXmlSupport.nodeSeqContentTypes.map(nodeSeqMarshaller): _*)
|
||||
Marshaller.oneOf(ScalaXmlSupport.nodeSeqMediaTypes.map(nodeSeqMarshaller): _*)
|
||||
|
||||
def nodeSeqMarshaller(contentType: ContentType): ToEntityMarshaller[NodeSeq] =
|
||||
Marshaller.StringMarshaller.wrap(contentType)(_.toString())
|
||||
def nodeSeqMarshaller(mediaType: MediaType.NonBinary): ToEntityMarshaller[NodeSeq] =
|
||||
Marshaller.StringMarshaller.wrap(mediaType)(_.toString())
|
||||
|
||||
implicit def defaultNodeSeqUnmarshaller: FromEntityUnmarshaller[NodeSeq] =
|
||||
nodeSeqUnmarshaller(ScalaXmlSupport.nodeSeqContentTypeRanges: _*)
|
||||
|
|
@ -35,13 +34,12 @@ trait ScalaXmlSupport {
|
|||
/**
|
||||
* Provides a SAXParser for the NodeSeqUnmarshaller to use. Override to provide a custom SAXParser implementation.
|
||||
* Will be called once for for every request to be unmarshalled. The default implementation calls [[ScalaXmlSupport.createSaferSAXParser]].
|
||||
* @return
|
||||
*/
|
||||
protected def createSAXParser(): SAXParser = ScalaXmlSupport.createSaferSAXParser()
|
||||
}
|
||||
object ScalaXmlSupport extends ScalaXmlSupport {
|
||||
val nodeSeqContentTypes: immutable.Seq[ContentType] = List(`text/xml`, `application/xml`, `text/html`, `application/xhtml+xml`)
|
||||
val nodeSeqContentTypeRanges: immutable.Seq[ContentTypeRange] = nodeSeqContentTypes.map(ContentTypeRange(_))
|
||||
val nodeSeqMediaTypes: immutable.Seq[MediaType.NonBinary] = List(`text/xml`, `application/xml`, `text/html`, `application/xhtml+xml`)
|
||||
val nodeSeqContentTypeRanges: immutable.Seq[ContentTypeRange] = nodeSeqMediaTypes.map(ContentTypeRange(_))
|
||||
|
||||
/** Creates a safer SAXParser. */
|
||||
def createSaferSAXParser(): SAXParser = {
|
||||
|
|
|
|||
|
|
@ -52,22 +52,24 @@ trait RouteTest extends RequestBuilding with WSTestRequestBuilding with RouteTes
|
|||
|
||||
def check[T](body: ⇒ T): RouteTestResult ⇒ T = result ⇒ dynRR.withValue(result.awaitResult)(body)
|
||||
|
||||
private def responseSafe = if (dynRR.value ne null) dynRR.value.response else "<not available anymore>"
|
||||
|
||||
def handled: Boolean = result.handled
|
||||
def response: HttpResponse = result.response
|
||||
def responseEntity: HttpEntity = result.entity
|
||||
def chunks: immutable.Seq[HttpEntity.ChunkStreamPart] = result.chunks
|
||||
def entityAs[T: FromEntityUnmarshaller: ClassTag](implicit timeout: Duration = 1.second): T = {
|
||||
def msg(e: Throwable) = s"Could not unmarshal entity to type '${implicitly[ClassTag[T]]}' for `entityAs` assertion: $e\n\nResponse was: $response"
|
||||
def msg(e: Throwable) = s"Could not unmarshal entity to type '${implicitly[ClassTag[T]]}' for `entityAs` assertion: $e\n\nResponse was: $responseSafe"
|
||||
Await.result(Unmarshal(responseEntity).to[T].fast.recover[T] { case error ⇒ failTest(msg(error)) }, timeout)
|
||||
}
|
||||
def responseAs[T: FromResponseUnmarshaller: ClassTag](implicit timeout: Duration = 1.second): T = {
|
||||
def msg(e: Throwable) = s"Could not unmarshal response to type '${implicitly[ClassTag[T]]}' for `responseAs` assertion: $e\n\nResponse was: $response"
|
||||
def msg(e: Throwable) = s"Could not unmarshal response to type '${implicitly[ClassTag[T]]}' for `responseAs` assertion: $e\n\nResponse was: $responseSafe"
|
||||
Await.result(Unmarshal(response).to[T].fast.recover[T] { case error ⇒ failTest(msg(error)) }, timeout)
|
||||
}
|
||||
def contentType: ContentType = responseEntity.contentType
|
||||
def mediaType: MediaType = contentType.mediaType
|
||||
def charset: HttpCharset = contentType.charset
|
||||
def definedCharset: Option[HttpCharset] = contentType.definedCharset
|
||||
def charsetOption: Option[HttpCharset] = contentType.charsetOption
|
||||
def charset: HttpCharset = charsetOption getOrElse sys.error("Binary entity does not have charset")
|
||||
def headers: immutable.Seq[HttpHeader] = response.headers
|
||||
def header[T <: HttpHeader: ClassTag]: Option[T] = response.header[T]
|
||||
def header(name: String): Option[HttpHeader] = response.headers.find(_.is(name.toLowerCase))
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
package akka.http.javadsl.server.values;
|
||||
|
||||
import akka.http.javadsl.model.HttpCharsets;
|
||||
import akka.http.javadsl.model.HttpRequest;
|
||||
import akka.http.javadsl.model.MediaTypes;
|
||||
import akka.http.javadsl.server.RequestVal;
|
||||
|
|
@ -50,7 +51,7 @@ public class FormFieldsTest extends JUnitRouteTest {
|
|||
|
||||
return
|
||||
HttpRequest.POST("/test")
|
||||
.withEntity(MediaTypes.APPLICATION_X_WWW_FORM_URLENCODED.toContentType(), sb.toString());
|
||||
.withEntity(MediaTypes.APPLICATION_X_WWW_FORM_URLENCODED.toContentType(HttpCharsets.UTF_8), sb.toString());
|
||||
}
|
||||
private HttpRequest singleParameterUrlEncodedRequest(String name, String value) {
|
||||
return urlEncodedRequest(entry(name, value));
|
||||
|
|
|
|||
|
|
@ -7,16 +7,15 @@ package akka.http.scaladsl.marshallers.xml
|
|||
import java.io.File
|
||||
|
||||
import akka.http.scaladsl.TestUtils
|
||||
import scala.concurrent.duration._
|
||||
import org.xml.sax.SAXParseException
|
||||
|
||||
import scala.concurrent.{ Future, Await }
|
||||
import scala.xml.NodeSeq
|
||||
import scala.concurrent.{ Future, Await }
|
||||
import scala.concurrent.duration._
|
||||
import org.scalatest.{ Inside, FreeSpec, Matchers }
|
||||
import akka.util.ByteString
|
||||
import akka.http.scaladsl.testkit.ScalatestRouteTest
|
||||
import akka.http.scaladsl.unmarshalling.{ Unmarshaller, Unmarshal }
|
||||
import akka.http.scaladsl.model._
|
||||
import HttpCharsets._
|
||||
import MediaTypes._
|
||||
|
||||
class ScalaXmlSupportSpec extends FreeSpec with Matchers with ScalatestRouteTest with Inside {
|
||||
|
|
@ -25,13 +24,13 @@ class ScalaXmlSupportSpec extends FreeSpec with Matchers with ScalatestRouteTest
|
|||
"NodeSeqMarshaller should" - {
|
||||
"marshal xml snippets to `text/xml` content in UTF-8" in {
|
||||
marshal(<employee><nr>Ha“llo</nr></employee>) shouldEqual
|
||||
HttpEntity(ContentType(`text/xml`, `UTF-8`), "<employee><nr>Ha“llo</nr></employee>")
|
||||
HttpEntity(ContentTypes.`text/xml(UTF-8)`, "<employee><nr>Ha“llo</nr></employee>")
|
||||
}
|
||||
"unmarshal `text/xml` content in UTF-8 to NodeSeqs" in {
|
||||
Unmarshal(HttpEntity(`text/xml`, "<int>Hällö</int>")).to[NodeSeq].map(_.text) should evaluateTo("Hällö")
|
||||
Unmarshal(HttpEntity(ContentTypes.`text/xml(UTF-8)`, "<int>Hällö</int>")).to[NodeSeq].map(_.text) should evaluateTo("Hällö")
|
||||
}
|
||||
"reject `application/octet-stream`" in {
|
||||
Unmarshal(HttpEntity(`application/octet-stream`, "<int>Hällö</int>")).to[NodeSeq].map(_.text) should
|
||||
Unmarshal(HttpEntity(`application/octet-stream`, ByteString("<int>Hällö</int>"))).to[NodeSeq].map(_.text) should
|
||||
haveFailedWith(Unmarshaller.UnsupportedContentTypeException(nodeSeqContentTypeRanges: _*))
|
||||
}
|
||||
|
||||
|
|
@ -43,7 +42,7 @@ class ScalaXmlSupportSpec extends FreeSpec with Matchers with ScalatestRouteTest
|
|||
| <!ELEMENT foo ANY >
|
||||
| <!ENTITY xxe SYSTEM "${f.toURI}">]><foo>hello&xxe;</foo>""".stripMargin
|
||||
|
||||
shouldHaveFailedWithSAXParseException(Unmarshal(HttpEntity(`text/xml`, xml)).to[NodeSeq])
|
||||
shouldHaveFailedWithSAXParseException(Unmarshal(HttpEntity(ContentTypes.`text/xml(UTF-8)`, xml)).to[NodeSeq])
|
||||
}
|
||||
}
|
||||
"parse XML bodies without loading in a related schema from a parameter" in {
|
||||
|
|
@ -58,7 +57,7 @@ class ScalaXmlSupportSpec extends FreeSpec with Matchers with ScalatestRouteTest
|
|||
| %xpe;
|
||||
| %pe;
|
||||
| ]><foo>hello&xxe;</foo>""".stripMargin
|
||||
shouldHaveFailedWithSAXParseException(Unmarshal(HttpEntity(`text/xml`, xml)).to[NodeSeq])
|
||||
shouldHaveFailedWithSAXParseException(Unmarshal(HttpEntity(ContentTypes.`text/xml(UTF-8)`, xml)).to[NodeSeq])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -73,7 +72,7 @@ class ScalaXmlSupportSpec extends FreeSpec with Matchers with ScalatestRouteTest
|
|||
| ]>
|
||||
| <billion>&laugh30;</billion>""".stripMargin
|
||||
|
||||
shouldHaveFailedWithSAXParseException(Unmarshal(HttpEntity(`text/xml`, xml)).to[NodeSeq])
|
||||
shouldHaveFailedWithSAXParseException(Unmarshal(HttpEntity(ContentTypes.`text/xml(UTF-8)`, xml)).to[NodeSeq])
|
||||
}
|
||||
"gracefully fail when an entity expands to be very large" in {
|
||||
val as = "a" * 50000
|
||||
|
|
@ -83,7 +82,7 @@ class ScalaXmlSupportSpec extends FreeSpec with Matchers with ScalatestRouteTest
|
|||
| <!ENTITY a "$as">
|
||||
| ]>
|
||||
| <kaboom>$entities</kaboom>""".stripMargin
|
||||
shouldHaveFailedWithSAXParseException(Unmarshal(HttpEntity(`text/xml`, xml)).to[NodeSeq])
|
||||
shouldHaveFailedWithSAXParseException(Unmarshal(HttpEntity(ContentTypes.`text/xml(UTF-8)`, xml)).to[NodeSeq])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@
|
|||
|
||||
package akka.http.scaladsl.marshalling
|
||||
|
||||
import akka.http.scaladsl.model.MediaType.Encoding
|
||||
|
||||
import scala.concurrent.Await
|
||||
import scala.concurrent.duration._
|
||||
import akka.http.scaladsl.server.ContentNegotiator.Alternative
|
||||
import akka.util.ByteString
|
||||
import org.scalatest.{ Matchers, FreeSpec }
|
||||
import akka.http.scaladsl.util.FastFuture._
|
||||
import akka.http.scaladsl.model._
|
||||
|
|
@ -19,29 +19,30 @@ class ContentNegotiationSpec extends FreeSpec with Matchers {
|
|||
"Content Negotiation should work properly for requests with header(s)" - {
|
||||
|
||||
"(without headers)" test { accept ⇒
|
||||
accept(`text/plain`) should select(`text/plain`, `UTF-8`)
|
||||
accept(`text/plain` withCharset `UTF-16`) should select(`text/plain`, `UTF-16`)
|
||||
accept(`text/plain` withCharset `UTF-16`) should select(`text/plain` withCharset `UTF-16`)
|
||||
accept(`text/plain`, `text/html`) should select(`text/plain` withCharset `UTF-8`)
|
||||
accept(`text/html`, `text/plain`) should select(`text/html` withCharset `UTF-8`)
|
||||
}
|
||||
|
||||
"Accept: */*" test { accept ⇒
|
||||
accept(`text/plain`) should select(`text/plain`, `UTF-8`)
|
||||
accept(`text/plain` withCharset `UTF-16`) should select(`text/plain`, `UTF-16`)
|
||||
accept(`text/plain`) should select(`text/plain` withCharset `UTF-8`)
|
||||
accept(`text/plain` withCharset `UTF-16`) should select(`text/plain` withCharset `UTF-16`)
|
||||
}
|
||||
|
||||
"Accept: */*;q=.8" test { accept ⇒
|
||||
accept(`text/plain`) should select(`text/plain`, `UTF-8`)
|
||||
accept(`text/plain` withCharset `UTF-16`) should select(`text/plain`, `UTF-16`)
|
||||
accept(`text/plain`) should select(`text/plain` withCharset `UTF-8`)
|
||||
accept(`text/plain` withCharset `UTF-16`) should select(`text/plain` withCharset `UTF-16`)
|
||||
}
|
||||
|
||||
"Accept: text/*" test { accept ⇒
|
||||
accept(`text/plain`) should select(`text/plain`, `UTF-8`)
|
||||
accept(`text/xml` withCharset `UTF-16`) should select(`text/xml`, `UTF-16`)
|
||||
accept(`text/plain`) should select(`text/plain` withCharset `UTF-8`)
|
||||
accept(`text/xml` withCharset `UTF-16`) should select(`text/xml` withCharset `UTF-16`)
|
||||
accept(`audio/ogg`) should reject
|
||||
}
|
||||
|
||||
"Accept: text/*;q=.8" test { accept ⇒
|
||||
accept(`text/plain`) should select(`text/plain`, `UTF-8`)
|
||||
accept(`text/xml` withCharset `UTF-16`) should select(`text/xml`, `UTF-16`)
|
||||
accept(`text/plain`) should select(`text/plain` withCharset `UTF-8`)
|
||||
accept(`text/xml` withCharset `UTF-16`) should select(`text/xml` withCharset `UTF-16`)
|
||||
accept(`audio/ogg`) should reject
|
||||
}
|
||||
|
||||
|
|
@ -51,39 +52,43 @@ class ContentNegotiationSpec extends FreeSpec with Matchers {
|
|||
accept(`audio/ogg`) should reject
|
||||
}
|
||||
|
||||
"Accept: text/*, application/json;q=0.8, text/plain;q=0.5" test { accept ⇒
|
||||
accept(`text/plain`, `application/json`) should select(`application/json`)
|
||||
}
|
||||
|
||||
"Accept-Charset: UTF-16" test { accept ⇒
|
||||
accept(`text/plain`) should select(`text/plain`, `UTF-16`)
|
||||
accept(`text/plain`) should select(`text/plain` withCharset `UTF-16`)
|
||||
accept(`text/plain` withCharset `UTF-8`) should reject
|
||||
}
|
||||
|
||||
"manually created Accept-Charset: UTF-16" in testHeaders(headers.`Accept-Charset`(Vector(HttpCharsets.`UTF-16`.toRange))) { accept ⇒
|
||||
accept(`text/plain`) should select(`text/plain`, `UTF-16`)
|
||||
accept(`text/plain`) should select(`text/plain` withCharset `UTF-16`)
|
||||
accept(`text/plain` withCharset `UTF-8`) should reject
|
||||
}
|
||||
|
||||
"Accept-Charset: UTF-16, UTF-8" test { accept ⇒
|
||||
accept(`text/plain`) should select(`text/plain`, `UTF-8`)
|
||||
accept(`text/plain` withCharset `UTF-16`) should select(`text/plain`, `UTF-16`)
|
||||
accept(`text/plain`) should select(`text/plain` withCharset `UTF-16`)
|
||||
accept(`text/plain` withCharset `UTF-8`) should select(`text/plain` withCharset `UTF-8`)
|
||||
}
|
||||
|
||||
"Accept-Charset: UTF-8;q=.2, UTF-16" test { accept ⇒
|
||||
accept(`text/plain`) should select(`text/plain`, `UTF-16`)
|
||||
accept(`text/plain` withCharset `UTF-8`) should select(`text/plain`, `UTF-8`)
|
||||
accept(`text/plain`) should select(`text/plain` withCharset `UTF-16`)
|
||||
accept(`text/plain` withCharset `UTF-8`) should select(`text/plain` withCharset `UTF-8`)
|
||||
}
|
||||
|
||||
"Accept-Charset: ISO-8859-1;q=.2" test { accept ⇒
|
||||
accept(`text/plain`) should select(`text/plain`, `ISO-8859-1`)
|
||||
accept(`text/plain`) should select(`text/plain` withCharset `ISO-8859-1`)
|
||||
accept(`text/plain` withCharset `UTF-8`) should reject
|
||||
}
|
||||
|
||||
"Accept-Charset: latin1;q=.1, UTF-8;q=.2" test { accept ⇒
|
||||
accept(`text/plain`) should select(`text/plain`, `UTF-8`)
|
||||
accept(`text/plain` withCharset `UTF-8`) should select(`text/plain`, `UTF-8`)
|
||||
accept(`text/plain`) should select(`text/plain` withCharset `UTF-8`)
|
||||
accept(`text/plain` withCharset `UTF-8`) should select(`text/plain` withCharset `UTF-8`)
|
||||
}
|
||||
|
||||
"Accept-Charset: *" test { accept ⇒
|
||||
accept(`text/plain`) should select(`text/plain`, `UTF-8`)
|
||||
accept(`text/plain` withCharset `UTF-16`) should select(`text/plain`, `UTF-16`)
|
||||
accept(`text/plain`) should select(`text/plain` withCharset `UTF-8`)
|
||||
accept(`text/plain` withCharset `UTF-16`) should select(`text/plain` withCharset `UTF-16`)
|
||||
}
|
||||
|
||||
"Accept-Charset: *;q=0" test { accept ⇒
|
||||
|
|
@ -92,47 +97,53 @@ class ContentNegotiationSpec extends FreeSpec with Matchers {
|
|||
}
|
||||
|
||||
"Accept-Charset: us;q=0.1,*;q=0" test { accept ⇒
|
||||
accept(`text/plain`) should select(`text/plain`, `US-ASCII`)
|
||||
accept(`text/plain`) should select(`text/plain` withCharset `US-ASCII`)
|
||||
accept(`text/plain` withCharset `UTF-8`) should reject
|
||||
}
|
||||
|
||||
"Accept-Charset: UTF-8, *;q=0.8, us;q=0.1" test { accept ⇒
|
||||
accept(`text/plain` withCharset `US-ASCII`,
|
||||
`text/plain` withCharset `ISO-8859-1`) should select(`text/plain` withCharset `ISO-8859-1`)
|
||||
}
|
||||
|
||||
"Accept: text/xml, text/html;q=.5" test { accept ⇒
|
||||
accept(`text/plain`) should reject
|
||||
accept(`text/xml`) should select(`text/xml`, `UTF-8`)
|
||||
accept(`text/html`) should select(`text/html`, `UTF-8`)
|
||||
accept(`text/html`, `text/xml`) should select(`text/xml`, `UTF-8`)
|
||||
accept(`text/xml`, `text/html`) should select(`text/xml`, `UTF-8`)
|
||||
accept(`text/plain`, `text/xml`) should select(`text/xml`, `UTF-8`)
|
||||
accept(`text/plain`, `text/html`) should select(`text/html`, `UTF-8`)
|
||||
accept(`text/xml`) should select(`text/xml` withCharset `UTF-8`)
|
||||
accept(`text/html`) should select(`text/html` withCharset `UTF-8`)
|
||||
accept(`text/html`, `text/xml`) should select(`text/xml` withCharset `UTF-8`)
|
||||
accept(`text/xml`, `text/html`) should select(`text/xml` withCharset `UTF-8`)
|
||||
accept(`text/plain`, `text/xml`) should select(`text/xml` withCharset `UTF-8`)
|
||||
accept(`text/plain`, `text/html`) should select(`text/html` withCharset `UTF-8`)
|
||||
}
|
||||
|
||||
"""Accept: text/html, text/plain;q=0.8, application/*;q=.5, *;q= .2
|
||||
|Accept-Charset: UTF-16""" test { accept ⇒
|
||||
accept(`text/plain`, `text/html`, `audio/ogg`) should select(`text/html`, `UTF-16`)
|
||||
accept(`text/plain`, `text/html` withCharset `UTF-8`, `audio/ogg`) should select(`text/plain`, `UTF-16`)
|
||||
accept(`audio/ogg`, `application/javascript`, `text/plain` withCharset `UTF-8`) should select(`application/javascript`, `UTF-16`)
|
||||
accept(`image/gif`, `application/javascript`) should select(`application/javascript`, `UTF-16`)
|
||||
accept(`text/plain`, `text/html`, `audio/ogg`) should select(`text/html` withCharset `UTF-16`)
|
||||
accept(`text/plain`, `text/html` withCharset `UTF-8`, `audio/ogg`) should select(`text/plain` withCharset `UTF-16`)
|
||||
accept(`audio/ogg`, `application/javascript`, `text/plain` withCharset `UTF-8`) should select(`application/javascript` withCharset `UTF-16`)
|
||||
accept(`image/gif`, `application/javascript`) should select(`application/javascript` withCharset `UTF-16`)
|
||||
accept(`image/gif`, `audio/ogg`) should select(`image/gif`)
|
||||
}
|
||||
|
||||
"Accept: text/xml, text/plain" test { accept ⇒
|
||||
accept(`text/plain` withCharset `UTF-16`) should select(`text/plain` withCharset `UTF-16`)
|
||||
accept(`text/plain`, `text/xml`) should select(`text/plain` withCharset `UTF-8`)
|
||||
accept(`text/xml`, `text/plain`) should select(`text/xml` withCharset `UTF-8`)
|
||||
}
|
||||
}
|
||||
|
||||
def testHeaders[U](headers: HttpHeader*)(body: ((ContentType*) ⇒ Option[ContentType]) ⇒ U): U = {
|
||||
def testHeaders[U](headers: HttpHeader*)(body: ((Alternative*) ⇒ Option[ContentType]) ⇒ U): U = {
|
||||
val request = HttpRequest(headers = headers.toVector)
|
||||
body { contentTypes ⇒
|
||||
body { alternatives ⇒
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
|
||||
// creates a pseudo marshaller for X, that applies for all the given content types
|
||||
trait X
|
||||
object X extends X
|
||||
implicit val marshallers: ToEntityMarshaller[X] =
|
||||
Marshaller.oneOf(contentTypes map {
|
||||
case ct @ ContentType(mt, Some(cs)) ⇒
|
||||
Marshaller.withFixedCharset(mt, cs)((s: X) ⇒ HttpEntity(ct, "The X"))
|
||||
case ContentType(mt, None) ⇒
|
||||
mt.encoding match {
|
||||
case Encoding.Open ⇒ Marshaller.withOpenCharset(mt)((s: X, cs) ⇒ HttpEntity(ContentType(mt, cs), "The X"))
|
||||
case _ ⇒ Marshaller.withOpenCharset(mt)((s: X, _) ⇒ HttpEntity(ContentType(mt, None), "The X"))
|
||||
}
|
||||
Marshaller.oneOf(alternatives map {
|
||||
case Alternative.ContentType(ct) ⇒ Marshaller.withFixedContentType(ct)((s: X) ⇒ HttpEntity(ct, ByteString("The X")))
|
||||
case Alternative.MediaType(mt) ⇒ Marshaller.withOpenCharset(mt)((s: X, cs) ⇒ HttpEntity(mt withCharset cs, "The X"))
|
||||
}: _*)
|
||||
|
||||
Await.result(Marshal(X).toResponseFor(request)
|
||||
|
|
@ -142,11 +153,10 @@ class ContentNegotiationSpec extends FreeSpec with Matchers {
|
|||
}
|
||||
|
||||
def reject = equal(None)
|
||||
def select(mediaType: MediaType, charset: HttpCharset) = equal(Some(ContentType(mediaType, charset)))
|
||||
def select(mediaType: MediaType) = equal(Some(ContentType(mediaType, None)))
|
||||
def select(contentType: ContentType) = equal(Some(contentType))
|
||||
|
||||
implicit class AddStringToIn(example: String) {
|
||||
def test(body: ((ContentType*) ⇒ Option[ContentType]) ⇒ Unit): Unit = example in {
|
||||
def test(body: ((Alternative*) ⇒ Option[ContentType]) ⇒ Unit): Unit = example in {
|
||||
val headers =
|
||||
if (example != "(without headers)") {
|
||||
example.stripMarginWithNewline("\n").split('\n').toList map { rawHeader ⇒
|
||||
|
|
|
|||
|
|
@ -4,14 +4,14 @@
|
|||
|
||||
package akka.http.scaladsl.marshalling
|
||||
|
||||
import akka.http.scaladsl.testkit.MarshallingTestUtils
|
||||
import akka.http.scaladsl.marshallers.xml.ScalaXmlSupport._
|
||||
|
||||
import scala.collection.immutable.ListMap
|
||||
import org.scalatest.{ BeforeAndAfterAll, FreeSpec, Matchers }
|
||||
import akka.util.ByteString
|
||||
import akka.actor.ActorSystem
|
||||
import akka.stream.ActorMaterializer
|
||||
import akka.stream.scaladsl.Source
|
||||
import akka.http.scaladsl.testkit.MarshallingTestUtils
|
||||
import akka.http.scaladsl.marshallers.xml.ScalaXmlSupport._
|
||||
import akka.http.impl.util._
|
||||
import akka.http.scaladsl.model._
|
||||
import headers._
|
||||
|
|
@ -32,7 +32,7 @@ class MarshallingSpec extends FreeSpec with Matchers with BeforeAndAfterAll with
|
|||
}
|
||||
"FormDataMarshaller should marshal FormData instances to application/x-www-form-urlencoded content" in {
|
||||
marshal(FormData(Map("name" -> "Bob", "pass" -> "hällo", "admin" -> ""))) shouldEqual
|
||||
HttpEntity(ContentType(`application/x-www-form-urlencoded`, `UTF-8`), "name=Bob&pass=h%C3%A4llo&admin=")
|
||||
HttpEntity(`application/x-www-form-urlencoded` withCharset `UTF-8`, "name=Bob&pass=h%C3%A4llo&admin=")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -52,7 +52,7 @@ class MarshallingSpec extends FreeSpec with Matchers with BeforeAndAfterAll with
|
|||
"multipartMarshaller should correctly marshal multipart content with" - {
|
||||
"one empty part" in {
|
||||
marshal(Multipart.General(`multipart/mixed`, Multipart.General.BodyPart.Strict(""))) shouldEqual HttpEntity(
|
||||
contentType = ContentType(`multipart/mixed` withBoundary randomBoundary),
|
||||
contentType = `multipart/mixed` withBoundary randomBoundary withCharset `UTF-8`,
|
||||
string = s"""--$randomBoundary
|
||||
|Content-Type: text/plain; charset=UTF-8
|
||||
|
|
||||
|
|
@ -61,10 +61,10 @@ class MarshallingSpec extends FreeSpec with Matchers with BeforeAndAfterAll with
|
|||
}
|
||||
"one non-empty part" in {
|
||||
marshal(Multipart.General(`multipart/alternative`, Multipart.General.BodyPart.Strict(
|
||||
entity = HttpEntity(ContentType(`text/plain`, `UTF-8`), "test@there.com"),
|
||||
entity = HttpEntity(ContentTypes.`text/plain(UTF-8)`, "test@there.com"),
|
||||
headers = `Content-Disposition`(ContentDispositionTypes.`form-data`, Map("name" -> "email")) :: Nil))) shouldEqual
|
||||
HttpEntity(
|
||||
contentType = ContentType(`multipart/alternative` withBoundary randomBoundary),
|
||||
contentType = `multipart/alternative` withBoundary randomBoundary withCharset `UTF-8`,
|
||||
string = s"""--$randomBoundary
|
||||
|Content-Type: text/plain; charset=UTF-8
|
||||
|Content-Disposition: form-data; name=email
|
||||
|
|
@ -74,12 +74,12 @@ class MarshallingSpec extends FreeSpec with Matchers with BeforeAndAfterAll with
|
|||
}
|
||||
"two different parts" in {
|
||||
marshal(Multipart.General(`multipart/related`,
|
||||
Multipart.General.BodyPart.Strict(HttpEntity(ContentType(`text/plain`, Some(`US-ASCII`)), "first part, with a trailing linebreak\r\n")),
|
||||
Multipart.General.BodyPart.Strict(HttpEntity(`text/plain` withCharset `US-ASCII`, "first part, with a trailing linebreak\r\n")),
|
||||
Multipart.General.BodyPart.Strict(
|
||||
HttpEntity(ContentType(`application/octet-stream`), "filecontent"),
|
||||
HttpEntity(`application/octet-stream`, ByteString("filecontent")),
|
||||
RawHeader("Content-Transfer-Encoding", "binary") :: Nil))) shouldEqual
|
||||
HttpEntity(
|
||||
contentType = ContentType(`multipart/related` withBoundary randomBoundary),
|
||||
contentType = `multipart/related` withBoundary randomBoundary withCharset `UTF-8`,
|
||||
string = s"""--$randomBoundary
|
||||
|Content-Type: text/plain; charset=US-ASCII
|
||||
|
|
||||
|
|
@ -100,7 +100,7 @@ class MarshallingSpec extends FreeSpec with Matchers with BeforeAndAfterAll with
|
|||
"surname" -> HttpEntity("Mike"),
|
||||
"age" -> marshal(<int>42</int>)))) shouldEqual
|
||||
HttpEntity(
|
||||
contentType = ContentType(`multipart/form-data` withBoundary randomBoundary),
|
||||
contentType = `multipart/form-data` withBoundary randomBoundary withCharset `UTF-8`,
|
||||
string = s"""--$randomBoundary
|
||||
|Content-Type: text/plain; charset=UTF-8
|
||||
|Content-Disposition: form-data; name=surname
|
||||
|
|
@ -116,14 +116,14 @@ class MarshallingSpec extends FreeSpec with Matchers with BeforeAndAfterAll with
|
|||
|
||||
"two fields having a custom `Content-Disposition`" in {
|
||||
marshal(Multipart.FormData(Source(List(
|
||||
Multipart.FormData.BodyPart("attachment[0]", HttpEntity(`text/csv`, "name,age\r\n\"John Doe\",20\r\n"),
|
||||
Multipart.FormData.BodyPart("attachment[0]", HttpEntity(`text/csv` withCharset `UTF-8`, "name,age\r\n\"John Doe\",20\r\n"),
|
||||
Map("filename" -> "attachment.csv")),
|
||||
Multipart.FormData.BodyPart("attachment[1]", HttpEntity("naice!".getBytes),
|
||||
Map("filename" -> "attachment2.csv"), List(RawHeader("Content-Transfer-Encoding", "binary"))))))) shouldEqual
|
||||
HttpEntity(
|
||||
contentType = ContentType(`multipart/form-data` withBoundary randomBoundary),
|
||||
contentType = `multipart/form-data` withBoundary randomBoundary withCharset `UTF-8`,
|
||||
string = s"""--$randomBoundary
|
||||
|Content-Type: text/csv
|
||||
|Content-Type: text/csv; charset=UTF-8
|
||||
|Content-Disposition: form-data; filename=attachment.csv; name="attachment[0]"
|
||||
|
|
||||
|name,age
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@ class CodingDirectivesSpec extends RoutingSpec with Inside {
|
|||
() ⇒ text.grouped(8).map { chars ⇒
|
||||
Chunk(chars.mkString): ChunkStreamPart
|
||||
}
|
||||
val chunkedTextEntity = HttpEntity.Chunked(MediaTypes.`text/plain`, Source(textChunks))
|
||||
val chunkedTextEntity = HttpEntity.Chunked(ContentTypes.`text/plain(UTF-8)`, Source(textChunks))
|
||||
|
||||
Post() ~> `Accept-Encoding`(gzip) ~> {
|
||||
encodeResponseWith(Gzip) {
|
||||
|
|
@ -348,6 +348,13 @@ class CodingDirectivesSpec extends RoutingSpec with Inside {
|
|||
response should haveContentEncoding(gzip)
|
||||
strictify(responseEntity) shouldEqual HttpEntity(ContentType(`text/plain`, `UTF-8`), yeahGzipped)
|
||||
}
|
||||
|
||||
Get("/") ~> `Accept-Encoding`(HttpEncodingRange.`*`, deflate withQValue 0.2) ~> {
|
||||
encodeResponseWith(Deflate, Gzip) { yeah }
|
||||
} ~> check {
|
||||
response should haveContentEncoding(gzip)
|
||||
strictify(responseEntity) shouldEqual HttpEntity(ContentType(`text/plain`, `UTF-8`), yeahGzipped)
|
||||
}
|
||||
}
|
||||
"reject the request if it has an Accept-Encoding header with an encoding that doesn't match" in {
|
||||
Get("/") ~> `Accept-Encoding`(deflate) ~> {
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ class FileAndResourceDirectivesSpec extends RoutingSpec with Inspectors with Ins
|
|||
writeAllText("This is PDF", file)
|
||||
Get() ~> getFromFile(file.getPath) ~> check {
|
||||
mediaType shouldEqual `application/pdf`
|
||||
definedCharset shouldEqual None
|
||||
charsetOption shouldEqual None
|
||||
responseAs[String] shouldEqual "This is PDF"
|
||||
headers should contain(`Last-Modified`(DateTime(file.lastModified)))
|
||||
}
|
||||
|
|
@ -71,7 +71,7 @@ class FileAndResourceDirectivesSpec extends RoutingSpec with Inspectors with Ins
|
|||
try {
|
||||
writeAllText("ABCDEFGHIJKLMNOPQRSTUVWXYZ", file)
|
||||
val rangeHeader = Range(ByteRange(1, 10), ByteRange.suffix(10))
|
||||
Get() ~> addHeader(rangeHeader) ~> getFromFile(file, ContentTypes.`text/plain`) ~> check {
|
||||
Get() ~> addHeader(rangeHeader) ~> getFromFile(file, ContentTypes.`text/plain(UTF-8)`) ~> check {
|
||||
status shouldEqual StatusCodes.PartialContent
|
||||
header[`Content-Range`] shouldEqual None
|
||||
mediaType.withParams(Map.empty) shouldEqual `multipart/byteranges`
|
||||
|
|
@ -91,6 +91,30 @@ class FileAndResourceDirectivesSpec extends RoutingSpec with Inspectors with Ins
|
|||
}
|
||||
} finally file.delete
|
||||
}
|
||||
|
||||
"support precompressed files with registered MediaType" in {
|
||||
val file = File.createTempFile("akkaHttpTest", ".svgz")
|
||||
try {
|
||||
writeAllText("123", file)
|
||||
Get() ~> getFromFile(file) ~> check {
|
||||
mediaType shouldEqual `image/svg+xml`
|
||||
header[`Content-Encoding`] shouldEqual Some(`Content-Encoding`(HttpEncodings.gzip))
|
||||
responseAs[String] shouldEqual "123"
|
||||
}
|
||||
} finally file.delete
|
||||
}
|
||||
|
||||
"support files with registered MediaType and .gz suffix" in {
|
||||
val file = File.createTempFile("akkaHttpTest", ".js.gz")
|
||||
try {
|
||||
writeAllText("456", file)
|
||||
Get() ~> getFromFile(file) ~> check {
|
||||
mediaType shouldEqual `application/javascript`
|
||||
header[`Content-Encoding`] shouldEqual Some(`Content-Encoding`(HttpEncodings.gzip))
|
||||
responseAs[String] shouldEqual "456"
|
||||
}
|
||||
} finally file.delete
|
||||
}
|
||||
}
|
||||
|
||||
"getFromResource" should {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ package akka.http.scaladsl.server.directives
|
|||
|
||||
import java.io.{ FileInputStream, File }
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
import akka.http.scaladsl.model._
|
||||
import akka.http.scaladsl.server.{ MissingFormFieldRejection, RoutingSpec }
|
||||
import akka.util.ByteString
|
||||
|
|
@ -22,7 +21,7 @@ class FileUploadDirectivesSpec extends RoutingSpec {
|
|||
val simpleMultipartUpload =
|
||||
Multipart.FormData(Multipart.FormData.BodyPart.Strict(
|
||||
"fieldName",
|
||||
HttpEntity(MediaTypes.`text/xml`, xml),
|
||||
HttpEntity(ContentTypes.`text/xml(UTF-8)`, xml),
|
||||
Map("filename" -> "age.xml")))
|
||||
|
||||
@volatile var file: Option[File] = None
|
||||
|
|
@ -36,7 +35,7 @@ class FileUploadDirectivesSpec extends RoutingSpec {
|
|||
}
|
||||
} ~> check {
|
||||
file.isDefined === true
|
||||
responseAs[String] === FileInfo("fieldName", "age.xml", ContentTypes.`text/xml`).toString
|
||||
responseAs[String] === FileInfo("fieldName", "age.xml", ContentTypes.`text/xml(UTF-8)`).toString
|
||||
read(file.get) === xml
|
||||
}
|
||||
} finally {
|
||||
|
|
@ -72,7 +71,7 @@ class FileUploadDirectivesSpec extends RoutingSpec {
|
|||
val multipartForm =
|
||||
Multipart.FormData(Multipart.FormData.BodyPart.Strict(
|
||||
"field1",
|
||||
HttpEntity(MediaTypes.`text/plain`, str1),
|
||||
HttpEntity(ContentTypes.`text/plain(UTF-8)`, str1),
|
||||
Map("filename" -> "data1.txt")))
|
||||
|
||||
Post("/", multipartForm) ~> route ~> check {
|
||||
|
|
@ -93,11 +92,11 @@ class FileUploadDirectivesSpec extends RoutingSpec {
|
|||
Multipart.FormData(
|
||||
Multipart.FormData.BodyPart.Strict(
|
||||
"field1",
|
||||
HttpEntity(MediaTypes.`text/plain`, str1),
|
||||
HttpEntity(ContentTypes.`text/plain(UTF-8)`, str1),
|
||||
Map("filename" -> "data1.txt")),
|
||||
Multipart.FormData.BodyPart.Strict(
|
||||
"field1",
|
||||
HttpEntity(MediaTypes.`text/plain`, str2),
|
||||
HttpEntity(ContentTypes.`text/plain(UTF-8)`, str2),
|
||||
Map("filename" -> "data2.txt")))
|
||||
|
||||
Post("/", multipartForm) ~> route ~> check {
|
||||
|
|
@ -130,7 +129,7 @@ class FileUploadDirectivesSpec extends RoutingSpec {
|
|||
val multipartForm =
|
||||
Multipart.FormData(Multipart.FormData.BodyPart.Strict(
|
||||
"field1",
|
||||
HttpEntity(MediaTypes.`text/plain`, str1),
|
||||
HttpEntity(ContentTypes.`text/plain(UTF-8)`, str1),
|
||||
Map("filename" -> "data1.txt")))
|
||||
|
||||
Post("/", multipartForm) ~> route ~> check {
|
||||
|
|
|
|||
|
|
@ -24,18 +24,18 @@ class FormFieldDirectivesSpec extends RoutingSpec {
|
|||
val multipartForm = Multipart.FormData {
|
||||
Map(
|
||||
"firstName" -> HttpEntity("Mike"),
|
||||
"age" -> HttpEntity(`text/xml`, "<int>42</int>"),
|
||||
"age" -> HttpEntity(ContentTypes.`text/xml(UTF-8)`, "<int>42</int>"),
|
||||
"VIPBoolean" -> HttpEntity("true"))
|
||||
}
|
||||
val multipartFormWithTextHtml = Multipart.FormData {
|
||||
Map(
|
||||
"firstName" -> HttpEntity("Mike"),
|
||||
"age" -> HttpEntity(`text/xml`, "<int>42</int>"),
|
||||
"VIP" -> HttpEntity(`text/html`, "<b>yes</b>"),
|
||||
"age" -> HttpEntity(ContentTypes.`text/xml(UTF-8)`, "<int>42</int>"),
|
||||
"VIP" -> HttpEntity(ContentTypes.`text/html(UTF-8)`, "<b>yes</b>"),
|
||||
"super" -> HttpEntity("no"))
|
||||
}
|
||||
val multipartFormWithFile = Multipart.FormData(
|
||||
Multipart.FormData.BodyPart.Strict("file", HttpEntity(MediaTypes.`text/xml`, "<int>42</int>"),
|
||||
Multipart.FormData.BodyPart.Strict("file", HttpEntity(ContentTypes.`text/xml(UTF-8)`, "<int>42</int>"),
|
||||
Map("filename" -> "age.xml")))
|
||||
|
||||
"The 'formFields' extraction directive" should {
|
||||
|
|
|
|||
|
|
@ -12,10 +12,10 @@ import akka.http.scaladsl.unmarshalling._
|
|||
import akka.http.scaladsl.marshalling._
|
||||
import akka.http.scaladsl.model._
|
||||
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
|
||||
import spray.json.DefaultJsonProtocol._
|
||||
import MediaTypes._
|
||||
import HttpCharsets._
|
||||
import headers._
|
||||
import spray.json.DefaultJsonProtocol._
|
||||
|
||||
class MarshallingDirectivesSpec extends RoutingSpec with Inside {
|
||||
import ScalaXmlSupport._
|
||||
|
|
@ -27,9 +27,10 @@ class MarshallingDirectivesSpec extends RoutingSpec with Inside {
|
|||
case x ⇒ { val i = x.text.toInt; require(i >= 0); i }
|
||||
}
|
||||
|
||||
val `text/xxml` = MediaType.customWithFixedCharset("text", "xxml", `UTF-8`)
|
||||
implicit val IntMarshaller: ToEntityMarshaller[Int] =
|
||||
Marshaller.oneOf(ContentType(`application/xhtml+xml`), ContentType(`text/xml`, `UTF-8`)) { contentType ⇒
|
||||
nodeSeqMarshaller(contentType).wrap(contentType) { (i: Int) ⇒ <int>{ i }</int> }
|
||||
Marshaller.oneOf[MediaType.NonBinary, Int, MessageEntity](`application/xhtml+xml`, `text/xxml`) { mediaType ⇒
|
||||
nodeSeqMarshaller(mediaType).wrap(mediaType) { (i: Int) ⇒ <int>{ i }</int> }
|
||||
}
|
||||
|
||||
"The 'entityAs' directive" should {
|
||||
|
|
@ -44,7 +45,7 @@ class MarshallingDirectivesSpec extends RoutingSpec with Inside {
|
|||
} ~> check { rejection shouldEqual RequestEntityExpectedRejection }
|
||||
}
|
||||
"return an UnsupportedRequestContentTypeRejection if no matching unmarshaller is in scope" in {
|
||||
Put("/", HttpEntity(`text/css`, "<p>cool</p>")) ~> {
|
||||
Put("/", HttpEntity(`text/css` withCharset `UTF-8`, "<p>cool</p>")) ~> {
|
||||
entity(as[NodeSeq]) { echoComplete }
|
||||
} ~> check {
|
||||
rejection shouldEqual UnsupportedRequestContentTypeRejection(Set(`text/xml`, `application/xml`, `text/html`, `application/xhtml+xml`))
|
||||
|
|
@ -56,7 +57,7 @@ class MarshallingDirectivesSpec extends RoutingSpec with Inside {
|
|||
}
|
||||
}
|
||||
"cancel UnsupportedRequestContentTypeRejections if a subsequent `entity` directive succeeds" in {
|
||||
Put("/", HttpEntity(`text/plain`, "yeah")) ~> {
|
||||
Put("/", HttpEntity(ContentTypes.`text/plain(UTF-8)`, "yeah")) ~> {
|
||||
entity(as[NodeSeq]) { _ ⇒ completeOk } ~
|
||||
entity(as[String]) { _ ⇒ validate(false, "Problem") { completeOk } }
|
||||
} ~> check { rejection shouldEqual ValidationRejection("Problem") }
|
||||
|
|
@ -71,7 +72,7 @@ class MarshallingDirectivesSpec extends RoutingSpec with Inside {
|
|||
}
|
||||
}
|
||||
"return a MalformedRequestContentRejection if unmarshalling failed due to a not further classified error" in {
|
||||
Put("/", HttpEntity(`text/xml`, "<foo attr='illegal xml'")) ~> {
|
||||
Put("/", HttpEntity(ContentTypes.`text/xml(UTF-8)`, "<foo attr='illegal xml'")) ~> {
|
||||
entity(as[NodeSeq]) { _ ⇒ completeOk }
|
||||
} ~> check {
|
||||
rejection shouldEqual MalformedRequestContentRejection(
|
||||
|
|
@ -89,7 +90,7 @@ class MarshallingDirectivesSpec extends RoutingSpec with Inside {
|
|||
} ~> check { responseAs[String] shouldEqual "None" }
|
||||
}
|
||||
"return an UnsupportedRequestContentTypeRejection if no matching unmarshaller is in scope (for Option[T]s)" in {
|
||||
Put("/", HttpEntity(`text/css`, "<p>cool</p>")) ~> {
|
||||
Put("/", HttpEntity(`text/css` withCharset `UTF-8`, "<p>cool</p>")) ~> {
|
||||
entity(as[Option[NodeSeq]]) { echoComplete }
|
||||
} ~> check {
|
||||
rejection shouldEqual UnsupportedRequestContentTypeRejection(Set(`text/xml`, `application/xml`, `text/html`, `application/xhtml+xml`))
|
||||
|
|
@ -105,13 +106,13 @@ class MarshallingDirectivesSpec extends RoutingSpec with Inside {
|
|||
|
||||
val route = entity(as[Person]) { echoComplete }
|
||||
|
||||
Put("/", HttpEntity(`text/xml`, "<name>Peter Xml</name>")) ~> route ~> check {
|
||||
Put("/", HttpEntity(ContentTypes.`text/xml(UTF-8)`, "<name>Peter Xml</name>")) ~> route ~> check {
|
||||
responseAs[String] shouldEqual "Person(Peter Xml)"
|
||||
}
|
||||
Put("/", HttpEntity(`application/json`, """{ "name": "Paul Json" }""")) ~> route ~> check {
|
||||
responseAs[String] shouldEqual "Person(Paul Json)"
|
||||
}
|
||||
Put("/", HttpEntity(`text/plain`, """name = Sir Text }""")) ~> route ~> check {
|
||||
Put("/", HttpEntity(ContentTypes.`text/plain(UTF-8)`, """name = Sir Text }""")) ~> route ~> check {
|
||||
rejection shouldEqual UnsupportedRequestContentTypeRejection(Set(`application/json`, `text/xml`))
|
||||
}
|
||||
}
|
||||
|
|
@ -125,7 +126,7 @@ class MarshallingDirectivesSpec extends RoutingSpec with Inside {
|
|||
}
|
||||
"return a UnacceptedResponseContentTypeRejection rejection if no acceptable marshaller is in scope" in {
|
||||
Get() ~> Accept(`text/css`) ~> completeWith(instanceOf[Int]) { prod ⇒ prod(42) } ~> check {
|
||||
rejection shouldEqual UnacceptedResponseContentTypeRejection(Set(`application/xhtml+xml`, ContentType(`text/xml`, `UTF-8`)))
|
||||
rejection shouldEqual UnacceptedResponseContentTypeRejection(Set(`application/xhtml+xml`, `text/xxml`))
|
||||
}
|
||||
}
|
||||
"convert the response content to an accepted charset" in {
|
||||
|
|
@ -139,19 +140,19 @@ class MarshallingDirectivesSpec extends RoutingSpec with Inside {
|
|||
def times2(x: Int) = x * 2
|
||||
|
||||
"support proper round-trip content unmarshalling/marshalling to and from a function" in (
|
||||
Put("/", HttpEntity(`text/html`, "<int>42</int>")) ~> Accept(`text/xml`) ~> handleWith(times2)
|
||||
~> check { responseEntity shouldEqual HttpEntity(ContentType(`text/xml`, `UTF-8`), "<int>84</int>") })
|
||||
Put("/", HttpEntity(ContentTypes.`text/html(UTF-8)`, "<int>42</int>")) ~> Accept(`text/xxml`) ~> handleWith(times2)
|
||||
~> check { responseEntity shouldEqual HttpEntity(`text/xxml`, "<int>84</int>") })
|
||||
|
||||
"result in UnsupportedRequestContentTypeRejection rejection if there is no unmarshaller supporting the requests charset" in (
|
||||
Put("/", HttpEntity(`text/xml`, "<int>42</int>")) ~> Accept(`text/xml`) ~> handleWith(times2)
|
||||
Put("/", HttpEntity(ContentTypes.`text/xml(UTF-8)`, "<int>42</int>")) ~> Accept(`text/xml`) ~> handleWith(times2)
|
||||
~> check {
|
||||
rejection shouldEqual UnsupportedRequestContentTypeRejection(Set(ContentTypeRange(`text/xml`, iso88592), `text/html`))
|
||||
})
|
||||
|
||||
"result in an UnacceptedResponseContentTypeRejection rejection if there is no marshaller supporting the requests Accept-Charset header" in (
|
||||
Put("/", HttpEntity(`text/html`, "<int>42</int>")) ~> addHeaders(Accept(`text/xml`), `Accept-Charset`(`UTF-16`)) ~>
|
||||
Put("/", HttpEntity(ContentTypes.`text/html(UTF-8)`, "<int>42</int>")) ~> addHeaders(Accept(`text/xxml`), `Accept-Charset`(`UTF-16`)) ~>
|
||||
handleWith(times2) ~> check {
|
||||
rejection shouldEqual UnacceptedResponseContentTypeRejection(Set(`application/xhtml+xml`, ContentType(`text/xml`, `UTF-8`)))
|
||||
rejection shouldEqual UnacceptedResponseContentTypeRejection(Set(`application/xhtml+xml`, `text/xxml`))
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -163,7 +164,7 @@ class MarshallingDirectivesSpec extends RoutingSpec with Inside {
|
|||
|
||||
"render JSON with UTF-8 encoding if no `Accept-Charset` request header is present" in {
|
||||
Get() ~> complete(foo) ~> check {
|
||||
responseEntity shouldEqual HttpEntity(ContentType(`application/json`, `UTF-8`), foo.toJson.prettyPrint)
|
||||
responseEntity shouldEqual HttpEntity(`application/json`, foo.toJson.prettyPrint)
|
||||
}
|
||||
}
|
||||
"reject JSON rendering if an `Accept-Charset` request header requests a non-UTF-8 encoding" in {
|
||||
|
|
|
|||
|
|
@ -34,10 +34,6 @@ class MiscDirectivesSpec extends RoutingSpec {
|
|||
"the selectPreferredLanguage directive" should {
|
||||
"Accept-Language: de, en" test { selectFrom ⇒
|
||||
selectFrom("de", "en") shouldEqual "de"
|
||||
selectFrom("en", "de") shouldEqual "de"
|
||||
}
|
||||
"Accept-Language: en, de" test { selectFrom ⇒
|
||||
selectFrom("de", "en") shouldEqual "en"
|
||||
selectFrom("en", "de") shouldEqual "en"
|
||||
}
|
||||
"Accept-Language: en, de;q=.5" test { selectFrom ⇒
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ class RangeDirectivesSpec extends RoutingSpec with Inspectors with Inside {
|
|||
def entityData() = StreamUtils.oneTimeSource(Source.single(ByteString(content)))
|
||||
|
||||
Get() ~> addHeader(Range(ByteRange(5, 10), ByteRange(0, 1), ByteRange(1, 2))) ~> {
|
||||
wrs { complete(HttpEntity.Default(MediaTypes.`text/plain`, content.length, entityData())) }
|
||||
wrs { complete(HttpEntity.Default(ContentTypes.`text/plain(UTF-8)`, content.length, entityData())) }
|
||||
} ~> check {
|
||||
header[`Content-Range`] should be(None)
|
||||
val parts = Await.result(responseAs[Multipart.ByteRanges].parts.grouped(1000).runWith(Sink.head), 1.second)
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ package akka.http.scaladsl.server.directives
|
|||
|
||||
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
|
||||
import akka.http.scaladsl.marshallers.xml.ScalaXmlSupport
|
||||
import akka.stream.scaladsl.Sink
|
||||
import org.scalatest.FreeSpec
|
||||
import scala.concurrent.{ Future, Promise }
|
||||
import akka.testkit.EventFilter
|
||||
import akka.http.scaladsl.marshallers.xml.ScalaXmlSupport._
|
||||
import akka.http.scaladsl.marshalling._
|
||||
import akka.http.scaladsl.server._
|
||||
|
|
@ -16,9 +16,6 @@ import akka.http.scaladsl.model._
|
|||
import akka.http.impl.util._
|
||||
import headers._
|
||||
import StatusCodes._
|
||||
import MediaTypes._
|
||||
import scala.xml.NodeSeq
|
||||
import akka.testkit.EventFilter
|
||||
|
||||
class RouteDirectivesSpec extends FreeSpec with GenericRoutingSpec {
|
||||
|
||||
|
|
@ -122,7 +119,8 @@ class RouteDirectivesSpec extends FreeSpec with GenericRoutingSpec {
|
|||
} ~> check {
|
||||
response shouldEqual HttpResponse(
|
||||
status = 302,
|
||||
entity = HttpEntity(`text/html`, "The requested resource temporarily resides under <a href=\"/foo\">this URI</a>."),
|
||||
entity = HttpEntity(ContentTypes.`text/html(UTF-8)`,
|
||||
"The requested resource temporarily resides under <a href=\"/foo\">this URI</a>."),
|
||||
headers = Location("/foo") :: Nil)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import akka.http.scaladsl.util.FastFuture._
|
|||
import akka.http.impl.util._
|
||||
import akka.http.scaladsl.model.headers._
|
||||
import MediaTypes._
|
||||
import HttpCharsets._
|
||||
|
||||
class MultipartUnmarshallersSpec extends FreeSpec with Matchers with BeforeAndAfterAll with ScalatestUtils {
|
||||
implicit val system = ActorSystem(getClass.getSimpleName)
|
||||
|
|
@ -28,13 +29,13 @@ class MultipartUnmarshallersSpec extends FreeSpec with Matchers with BeforeAndAf
|
|||
|
||||
"multipartGeneralUnmarshaller should correctly unmarshal 'multipart/*' content with" - {
|
||||
"an empty part" in {
|
||||
Unmarshal(HttpEntity(`multipart/mixed` withBoundary "XYZABC",
|
||||
Unmarshal(HttpEntity(`multipart/mixed` withBoundary "XYZABC" withCharset `UTF-8`,
|
||||
"""--XYZABC
|
||||
|--XYZABC--""".stripMarginWithNewline("\r\n"))).to[Multipart.General] should haveParts(
|
||||
Multipart.General.BodyPart.Strict(HttpEntity.empty(ContentTypes.`text/plain(UTF-8)`)))
|
||||
}
|
||||
"two empty parts" in {
|
||||
Unmarshal(HttpEntity(`multipart/mixed` withBoundary "XYZABC",
|
||||
Unmarshal(HttpEntity(`multipart/mixed` withBoundary "XYZABC" withCharset `UTF-8`,
|
||||
"""--XYZABC
|
||||
|--XYZABC
|
||||
|--XYZABC--""".stripMarginWithNewline("\r\n"))).to[Multipart.General] should haveParts(
|
||||
|
|
@ -42,15 +43,15 @@ class MultipartUnmarshallersSpec extends FreeSpec with Matchers with BeforeAndAf
|
|||
Multipart.General.BodyPart.Strict(HttpEntity.empty(ContentTypes.`text/plain(UTF-8)`)))
|
||||
}
|
||||
"a part without entity and missing header separation CRLF" in {
|
||||
Unmarshal(HttpEntity(`multipart/mixed` withBoundary "XYZABC",
|
||||
Unmarshal(HttpEntity(`multipart/mixed` withBoundary "XYZABC" withCharset `UTF-8`,
|
||||
"""--XYZABC
|
||||
|Content-type: text/xml
|
||||
|Age: 12
|
||||
|--XYZABC--""".stripMarginWithNewline("\r\n"))).to[Multipart.General] should haveParts(
|
||||
Multipart.General.BodyPart.Strict(HttpEntity.empty(MediaTypes.`text/xml`), List(Age(12))))
|
||||
Multipart.General.BodyPart.Strict(HttpEntity.empty(ContentTypes.`text/xml(UTF-8)`), List(Age(12))))
|
||||
}
|
||||
"an implicitly typed part (without headers) (Strict)" in {
|
||||
Unmarshal(HttpEntity(`multipart/mixed` withBoundary "XYZABC",
|
||||
Unmarshal(HttpEntity(`multipart/mixed` withBoundary "XYZABC" withCharset `UTF-8`,
|
||||
"""--XYZABC
|
||||
|
|
||||
|Perfectly fine part content.
|
||||
|
|
@ -63,12 +64,12 @@ class MultipartUnmarshallersSpec extends FreeSpec with Matchers with BeforeAndAf
|
|||
|Perfectly fine part content.
|
||||
|--XYZABC--""".stripMarginWithNewline("\r\n")
|
||||
val byteStrings = content.map(c ⇒ ByteString(c.toString)) // one-char ByteStrings
|
||||
Unmarshal(HttpEntity.Default(`multipart/mixed` withBoundary "XYZABC", content.length, Source(byteStrings)))
|
||||
Unmarshal(HttpEntity.Default(`multipart/mixed` withBoundary "XYZABC" withCharset `UTF-8`, content.length, Source(byteStrings)))
|
||||
.to[Multipart.General] should haveParts(
|
||||
Multipart.General.BodyPart.Strict(HttpEntity(ContentTypes.`text/plain(UTF-8)`, "Perfectly fine part content.")))
|
||||
}
|
||||
"one non-empty form-data part" in {
|
||||
Unmarshal(HttpEntity(`multipart/form-data` withBoundary "-",
|
||||
Unmarshal(HttpEntity(`multipart/form-data` withBoundary "-" withCharset `UTF-8`,
|
||||
"""---
|
||||
|Content-type: text/plain; charset=UTF8
|
||||
|content-disposition: form-data; name="email"
|
||||
|
|
@ -80,7 +81,7 @@ class MultipartUnmarshallersSpec extends FreeSpec with Matchers with BeforeAndAf
|
|||
List(`Content-Disposition`(ContentDispositionTypes.`form-data`, Map("name" -> "email")))))
|
||||
}
|
||||
"two different parts" in {
|
||||
Unmarshal(HttpEntity(`multipart/mixed` withBoundary "12345",
|
||||
Unmarshal(HttpEntity(`multipart/mixed` withBoundary "12345" withCharset `UTF-8`,
|
||||
"""--12345
|
||||
|
|
||||
|first part, with a trailing newline
|
||||
|
|
@ -92,10 +93,11 @@ class MultipartUnmarshallersSpec extends FreeSpec with Matchers with BeforeAndAf
|
|||
|filecontent
|
||||
|--12345--""".stripMarginWithNewline("\r\n"))).to[Multipart.General] should haveParts(
|
||||
Multipart.General.BodyPart.Strict(HttpEntity(ContentTypes.`text/plain(UTF-8)`, "first part, with a trailing newline\r\n")),
|
||||
Multipart.General.BodyPart.Strict(HttpEntity(`application/octet-stream`, "filecontent"), List(RawHeader("Content-Transfer-Encoding", "binary"))))
|
||||
Multipart.General.BodyPart.Strict(HttpEntity(`application/octet-stream`, ByteString("filecontent")),
|
||||
List(RawHeader("Content-Transfer-Encoding", "binary"))))
|
||||
}
|
||||
"illegal headers" in (
|
||||
Unmarshal(HttpEntity(`multipart/form-data` withBoundary "XYZABC",
|
||||
Unmarshal(HttpEntity(`multipart/form-data` withBoundary "XYZABC" withCharset `UTF-8`,
|
||||
"""--XYZABC
|
||||
|Date: unknown
|
||||
|content-disposition: form-data; name=email
|
||||
|
|
@ -107,7 +109,7 @@ class MultipartUnmarshallersSpec extends FreeSpec with Matchers with BeforeAndAf
|
|||
List(RawHeader("date", "unknown"),
|
||||
`Content-Disposition`(ContentDispositionTypes.`form-data`, Map("name" -> "email"))))))
|
||||
"a full example (Strict)" in {
|
||||
Unmarshal(HttpEntity(`multipart/mixed` withBoundary "12345",
|
||||
Unmarshal(HttpEntity(`multipart/mixed` withBoundary "12345" withCharset `UTF-8`,
|
||||
"""preamble and
|
||||
|more preamble
|
||||
|--12345
|
||||
|
|
@ -121,7 +123,7 @@ class MultipartUnmarshallersSpec extends FreeSpec with Matchers with BeforeAndAf
|
|||
|epilogue and
|
||||
|more epilogue""".stripMarginWithNewline("\r\n"))).to[Multipart.General] should haveParts(
|
||||
Multipart.General.BodyPart.Strict(HttpEntity(ContentTypes.`text/plain(UTF-8)`, "first part, implicitly typed")),
|
||||
Multipart.General.BodyPart.Strict(HttpEntity(`application/octet-stream`, "second part, explicitly typed")))
|
||||
Multipart.General.BodyPart.Strict(HttpEntity(`application/octet-stream`, ByteString("second part, explicitly typed"))))
|
||||
}
|
||||
"a full example (Default)" in {
|
||||
val content = """preamble and
|
||||
|
|
@ -137,13 +139,13 @@ class MultipartUnmarshallersSpec extends FreeSpec with Matchers with BeforeAndAf
|
|||
|epilogue and
|
||||
|more epilogue""".stripMarginWithNewline("\r\n")
|
||||
val byteStrings = content.map(c ⇒ ByteString(c.toString)) // one-char ByteStrings
|
||||
Unmarshal(HttpEntity.Default(`multipart/mixed` withBoundary "12345", content.length, Source(byteStrings)))
|
||||
Unmarshal(HttpEntity.Default(`multipart/mixed` withBoundary "12345" withCharset `UTF-8`, content.length, Source(byteStrings)))
|
||||
.to[Multipart.General] should haveParts(
|
||||
Multipart.General.BodyPart.Strict(HttpEntity(ContentTypes.`text/plain(UTF-8)`, "first part, implicitly typed")),
|
||||
Multipart.General.BodyPart.Strict(HttpEntity(`application/octet-stream`, "second part, explicitly typed")))
|
||||
Multipart.General.BodyPart.Strict(HttpEntity(`application/octet-stream`, ByteString("second part, explicitly typed"))))
|
||||
}
|
||||
"a boundary with spaces" in {
|
||||
Unmarshal(HttpEntity(`multipart/mixed` withBoundary "simple boundary",
|
||||
Unmarshal(HttpEntity(`multipart/mixed` withBoundary "simple boundary" withCharset `UTF-8`,
|
||||
"""--simple boundary
|
||||
|--simple boundary--""".stripMarginWithNewline("\r\n"))).to[Multipart.General] should haveParts(
|
||||
Multipart.General.BodyPart.Strict(HttpEntity.empty(ContentTypes.`text/plain(UTF-8)`)))
|
||||
|
|
@ -152,17 +154,17 @@ class MultipartUnmarshallersSpec extends FreeSpec with Matchers with BeforeAndAf
|
|||
|
||||
"multipartGeneralUnmarshaller should reject illegal multipart content with" - {
|
||||
"an empty entity" in {
|
||||
Await.result(Unmarshal(HttpEntity(`multipart/mixed` withBoundary "XYZABC", ByteString.empty))
|
||||
Await.result(Unmarshal(HttpEntity(`multipart/mixed` withBoundary "XYZABC" withCharset `UTF-8`, ByteString.empty))
|
||||
.to[Multipart.General].failed, 1.second).getMessage shouldEqual "Unexpected end of multipart entity"
|
||||
}
|
||||
"an entity without initial boundary" in {
|
||||
Await.result(Unmarshal(HttpEntity(`multipart/mixed` withBoundary "XYZABC",
|
||||
Await.result(Unmarshal(HttpEntity(`multipart/mixed` withBoundary "XYZABC" withCharset `UTF-8`,
|
||||
"""this is
|
||||
|just preamble text""".stripMarginWithNewline("\r\n")))
|
||||
.to[Multipart.General].failed, 1.second).getMessage shouldEqual "Unexpected end of multipart entity"
|
||||
}
|
||||
"a stray boundary" in {
|
||||
Await.result(Unmarshal(HttpEntity(`multipart/form-data` withBoundary "ABC",
|
||||
Await.result(Unmarshal(HttpEntity(`multipart/form-data` withBoundary "ABC" withCharset `UTF-8`,
|
||||
"""--ABC
|
||||
|Content-type: text/plain; charset=UTF8
|
||||
|--ABCContent-type: application/json
|
||||
|
|
@ -171,7 +173,7 @@ class MultipartUnmarshallersSpec extends FreeSpec with Matchers with BeforeAndAf
|
|||
.to[Multipart.General].failed, 1.second).getMessage shouldEqual "Illegal multipart boundary in message content"
|
||||
}
|
||||
"duplicate Content-Type header" in {
|
||||
Await.result(Unmarshal(HttpEntity(`multipart/form-data` withBoundary "-",
|
||||
Await.result(Unmarshal(HttpEntity(`multipart/form-data` withBoundary "-" withCharset `UTF-8`,
|
||||
"""---
|
||||
|Content-type: text/plain; charset=UTF8
|
||||
|Content-type: application/json
|
||||
|
|
@ -183,7 +185,7 @@ class MultipartUnmarshallersSpec extends FreeSpec with Matchers with BeforeAndAf
|
|||
"multipart part must not contain more than one Content-Type header"
|
||||
}
|
||||
"a missing header-separating CRLF (in Strict entity)" in {
|
||||
Await.result(Unmarshal(HttpEntity(`multipart/form-data` withBoundary "-",
|
||||
Await.result(Unmarshal(HttpEntity(`multipart/form-data` withBoundary "-" withCharset `UTF-8`,
|
||||
"""---
|
||||
|not good here
|
||||
|-----""".stripMarginWithNewline("\r\n")))
|
||||
|
|
@ -197,27 +199,27 @@ class MultipartUnmarshallersSpec extends FreeSpec with Matchers with BeforeAndAf
|
|||
|not ok
|
||||
|-----""".stripMarginWithNewline("\r\n")
|
||||
val byteStrings = content.map(c ⇒ ByteString(c.toString)) // one-char ByteStrings
|
||||
val contentType = `multipart/form-data` withBoundary "-"
|
||||
val contentType = `multipart/form-data` withBoundary "-" withCharset `UTF-8`
|
||||
Await.result(Unmarshal(HttpEntity.Default(contentType, content.length, Source(byteStrings)))
|
||||
.to[Multipart.General]
|
||||
.flatMap(_ toStrict 1.second).failed, 1.second).getMessage shouldEqual "Illegal character ' ' in header name"
|
||||
}
|
||||
"a boundary with a trailing space" in {
|
||||
Await.result(
|
||||
Unmarshal(HttpEntity(`multipart/mixed` withBoundary "simple boundary ", ByteString.empty))
|
||||
Unmarshal(HttpEntity(`multipart/mixed` withBoundary "simple boundary " withCharset `UTF-8`, ByteString.empty))
|
||||
.to[Multipart.General].failed, 1.second).getMessage shouldEqual
|
||||
"requirement failed: 'boundary' parameter of multipart Content-Type must not end with a space char"
|
||||
}
|
||||
"a boundary with an illegal character" in {
|
||||
Await.result(
|
||||
Unmarshal(HttpEntity(`multipart/mixed` withBoundary "simple&boundary", ByteString.empty))
|
||||
Unmarshal(HttpEntity(`multipart/mixed` withBoundary "simple&boundary" withCharset `UTF-8`, ByteString.empty))
|
||||
.to[Multipart.General].failed, 1.second).getMessage shouldEqual
|
||||
"requirement failed: 'boundary' parameter of multipart Content-Type contains illegal character '&'"
|
||||
}
|
||||
}
|
||||
|
||||
"multipartByteRangesUnmarshaller should correctly unmarshal multipart/byteranges content with two different parts" in {
|
||||
Unmarshal(HttpEntity(`multipart/byteranges` withBoundary "12345",
|
||||
Unmarshal(HttpEntity(`multipart/byteranges` withBoundary "12345" withCharset `UTF-8`,
|
||||
"""--12345
|
||||
|Content-Range: bytes 0-2/26
|
||||
|Content-Type: text/plain
|
||||
|
|
@ -229,24 +231,24 @@ class MultipartUnmarshallersSpec extends FreeSpec with Matchers with BeforeAndAf
|
|||
|
|
||||
|XYZ
|
||||
|--12345--""".stripMarginWithNewline("\r\n"))).to[Multipart.ByteRanges] should haveParts(
|
||||
Multipart.ByteRanges.BodyPart.Strict(ContentRange(0, 2, 26), HttpEntity(ContentTypes.`text/plain`, "ABC")),
|
||||
Multipart.ByteRanges.BodyPart.Strict(ContentRange(23, 25, 26), HttpEntity(ContentTypes.`text/plain`, "XYZ")))
|
||||
Multipart.ByteRanges.BodyPart.Strict(ContentRange(0, 2, 26), HttpEntity(ContentTypes.`text/plain(UTF-8)`, "ABC")),
|
||||
Multipart.ByteRanges.BodyPart.Strict(ContentRange(23, 25, 26), HttpEntity(ContentTypes.`text/plain(UTF-8)`, "XYZ")))
|
||||
}
|
||||
|
||||
"multipartFormDataUnmarshaller should correctly unmarshal 'multipart/form-data' content" - {
|
||||
"with one element" in {
|
||||
Unmarshal(HttpEntity(`multipart/form-data` withBoundary "XYZABC",
|
||||
Unmarshal(HttpEntity(`multipart/form-data` withBoundary "XYZABC" withCharset `UTF-8`,
|
||||
"""--XYZABC
|
||||
|content-disposition: form-data; name=email
|
||||
|
|
||||
|test@there.com
|
||||
|--XYZABC--""".stripMarginWithNewline("\r\n"))).to[Multipart.FormData] should haveParts(
|
||||
Multipart.FormData.BodyPart.Strict("email", HttpEntity(ContentTypes.`application/octet-stream`, "test@there.com")))
|
||||
Multipart.FormData.BodyPart.Strict("email", HttpEntity(`application/octet-stream`, ByteString("test@there.com"))))
|
||||
}
|
||||
"with a file" in {
|
||||
Unmarshal {
|
||||
HttpEntity.Default(
|
||||
contentType = `multipart/form-data` withBoundary "XYZABC",
|
||||
contentType = `multipart/form-data` withBoundary "XYZABC" withCharset `UTF-8`,
|
||||
contentLength = 1, // not verified during unmarshalling
|
||||
data = Source {
|
||||
List(
|
||||
|
|
@ -270,8 +272,8 @@ class MultipartUnmarshallersSpec extends FreeSpec with Matchers with BeforeAndAf
|
|||
})
|
||||
})
|
||||
}.to[Multipart.FormData].flatMap(_.toStrict(1.second)) should haveParts(
|
||||
Multipart.FormData.BodyPart.Strict("email", HttpEntity(ContentTypes.`application/octet-stream`, "test@there.com")),
|
||||
Multipart.FormData.BodyPart.Strict("userfile", HttpEntity(MediaTypes.`application/pdf`, "filecontent"), Map("filename" -> "test.dat"),
|
||||
Multipart.FormData.BodyPart.Strict("email", HttpEntity(`application/octet-stream`, ByteString("test@there.com"))),
|
||||
Multipart.FormData.BodyPart.Strict("userfile", HttpEntity(`application/pdf`, ByteString("filecontent")), Map("filename" -> "test.dat"),
|
||||
List(
|
||||
RawHeader("Content-Transfer-Encoding", "binary"),
|
||||
RawHeader("Content-Additional-1", "anything"),
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ private[http] class RejectionHandlerWrapper(javaHandler: server.RejectionHandler
|
|||
case RequestEntityExpectedRejection ⇒
|
||||
handleRequestEntityExpectedRejection(ctx)
|
||||
case UnacceptedResponseContentTypeRejection(supported) ⇒
|
||||
handleUnacceptedResponseContentTypeRejection(ctx, supported.toList.toSeq.asJava)
|
||||
handleUnacceptedResponseContentTypeRejection(ctx, supported.toList.map(_.format).toSeq.asJava)
|
||||
case UnacceptedResponseEncodingRejection(supported) ⇒
|
||||
handleUnacceptedResponseEncodingRejection(ctx, supported.toList.toSeq.asJava)
|
||||
case AuthenticationFailedRejection(cause, challenge) ⇒
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ private[http] final case class RequestContextImpl(underlying: ScalaRequestContex
|
|||
case r: RouteResultImpl ⇒ r.underlying
|
||||
}(executionContext())
|
||||
def complete(text: String): RouteResult = underlying.complete(text)
|
||||
def complete(contentType: ContentType, text: String): RouteResult =
|
||||
def complete(contentType: ContentType.NonBinary, text: String): RouteResult =
|
||||
underlying.complete(HttpEntity(contentType.asScala, text))
|
||||
|
||||
def completeWithStatus(statusCode: Int): RouteResult =
|
||||
|
|
|
|||
|
|
@ -28,7 +28,14 @@ object Marshallers {
|
|||
* Creates a marshaller by specifying a media type and conversion function from ``T`` to String.
|
||||
* The charset for encoding the response will be negotiated with the client.
|
||||
*/
|
||||
def toEntityString[T](mediaType: MediaType, convert: function.Function[T, String]): Marshaller[T] =
|
||||
def toEntityString[T](mediaType: MediaType.WithOpenCharset, convert: function.Function[T, String]): Marshaller[T] =
|
||||
MarshallerImpl(_ ⇒ ScalaMarshaller.stringMarshaller(mediaType.asScala).compose[T](convert(_)))
|
||||
|
||||
/**
|
||||
* Creates a marshaller by specifying a media type and conversion function from ``T`` to String.
|
||||
* The charset for encoding the response will be negotiated with the client.
|
||||
*/
|
||||
def toEntityString[T](mediaType: MediaType.WithFixedCharset, convert: function.Function[T, String]): Marshaller[T] =
|
||||
MarshallerImpl(_ ⇒ ScalaMarshaller.stringMarshaller(mediaType.asScala).compose[T](convert(_)))
|
||||
|
||||
/**
|
||||
|
|
@ -48,7 +55,7 @@ object Marshallers {
|
|||
*/
|
||||
def toEntity[T](contentType: ContentType, convert: function.Function[T, ResponseEntity]): Marshaller[T] =
|
||||
MarshallerImpl { _ ⇒
|
||||
ScalaMarshaller.withFixedCharset(contentType.mediaType().asScala, contentType.charset().asScala)(t ⇒
|
||||
ScalaMarshaller.withFixedContentType(contentType.asScala)(t ⇒
|
||||
HttpResponse.create().withStatus(200).withEntity(convert(t)).asScala)
|
||||
}
|
||||
|
||||
|
|
@ -57,7 +64,6 @@ object Marshallers {
|
|||
*/
|
||||
def toResponse[T](contentType: ContentType, convert: function.Function[T, HttpResponse]): Marshaller[T] =
|
||||
MarshallerImpl { _ ⇒
|
||||
ScalaMarshaller.withFixedCharset(contentType.mediaType().asScala, contentType.charset().asScala)(t ⇒
|
||||
convert(t).asScala)
|
||||
ScalaMarshaller.withFixedContentType(contentType.asScala)(t ⇒ convert(t).asScala)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ package akka.http.javadsl.server
|
|||
import java.{ lang ⇒ jl }
|
||||
|
||||
import akka.http.impl.server.PassRejectionRouteResult
|
||||
import akka.http.javadsl.model.{ ContentType, ContentTypeRange, HttpMethod }
|
||||
import akka.http.javadsl.model.{ ContentTypeRange, HttpMethod }
|
||||
import akka.http.javadsl.model.headers.{ HttpChallenge, ByteRange, HttpEncoding }
|
||||
import akka.http.scaladsl.server.Rejection
|
||||
|
||||
|
|
@ -122,9 +122,9 @@ abstract class RejectionHandler {
|
|||
/**
|
||||
* Callback called to handle rejection created by marshallers.
|
||||
* Signals that the request was rejected because the service is not capable of producing a response entity whose
|
||||
* content type is accepted by the client
|
||||
* content type is accepted by the client.
|
||||
*/
|
||||
def handleUnacceptedResponseContentTypeRejection(ctx: RequestContext, supported: jl.Iterable[ContentType]): RouteResult = passRejection()
|
||||
def handleUnacceptedResponseContentTypeRejection(ctx: RequestContext, supported: jl.Iterable[String]): RouteResult = passRejection()
|
||||
|
||||
/**
|
||||
* Callback called to handle rejection created by encoding filters.
|
||||
|
|
|
|||
|
|
@ -4,11 +4,9 @@
|
|||
|
||||
package akka.http.javadsl.server
|
||||
|
||||
import scala.concurrent.{ ExecutionContext, Future }
|
||||
import akka.http.javadsl.model._
|
||||
import akka.stream.Materializer
|
||||
import akka.util.ByteString
|
||||
|
||||
import scala.concurrent.{ ExecutionContext, Future }
|
||||
|
||||
/**
|
||||
* The RequestContext represents the state of the request while it is routed through
|
||||
|
|
@ -50,7 +48,7 @@ trait RequestContext {
|
|||
/**
|
||||
* Completes the request with the given string as an entity of the given type.
|
||||
*/
|
||||
def complete(contentType: ContentType, text: String): RouteResult
|
||||
def complete(contentType: ContentType.NonBinary, text: String): RouteResult
|
||||
|
||||
/**
|
||||
* Completes the request with the given status code and no entity.
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ abstract class BasicDirectives extends BasicDirectivesBase {
|
|||
/**
|
||||
* A route that completes the request with a static text
|
||||
*/
|
||||
def complete(contentType: ContentType, text: String): Route =
|
||||
def complete(contentType: ContentType.NonBinary, text: String): Route =
|
||||
new OpaqueRoute() {
|
||||
def handle(ctx: RequestContext): RouteResult =
|
||||
ctx.complete(contentType, text)
|
||||
|
|
|
|||
|
|
@ -36,11 +36,6 @@ trait FileAndResourceRoute extends Route {
|
|||
*/
|
||||
def withContentType(contentType: ContentType): Route
|
||||
|
||||
/**
|
||||
* Returns a variant of this route that responds with the given constant [[MediaType]].
|
||||
*/
|
||||
def withContentType(mediaType: MediaType): Route
|
||||
|
||||
/**
|
||||
* Returns a variant of this route that uses the specified [[ContentTypeResolver]] to determine
|
||||
* which [[ContentType]] to respond with by file name.
|
||||
|
|
@ -55,8 +50,6 @@ object FileAndResourceRoute {
|
|||
private[http] def apply(f: ContentTypeResolver ⇒ Route): FileAndResourceRoute =
|
||||
new FileAndResourceRouteWithDefaultResolver(f) with FileAndResourceRoute {
|
||||
def withContentType(contentType: ContentType): Route = resolveContentTypeWith(StaticContentTypeResolver(contentType))
|
||||
def withContentType(mediaType: MediaType): Route = withContentType(mediaType.toContentType)
|
||||
|
||||
def resolveContentTypeWith(resolver: ContentTypeResolver): Route = f(resolver)
|
||||
}
|
||||
|
||||
|
|
@ -66,8 +59,6 @@ object FileAndResourceRoute {
|
|||
private[http] def forFixedName(fileName: String)(f: ContentType ⇒ Route): FileAndResourceRoute =
|
||||
new FileAndResourceRouteWithDefaultResolver(resolver ⇒ f(resolver.resolve(fileName))) with FileAndResourceRoute {
|
||||
def withContentType(contentType: ContentType): Route = resolveContentTypeWith(StaticContentTypeResolver(contentType))
|
||||
def withContentType(mediaType: MediaType): Route = withContentType(mediaType.toContentType)
|
||||
|
||||
def resolveContentTypeWith(resolver: ContentTypeResolver): Route = f(resolver.resolve(fileName))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ object Encoder {
|
|||
case res @ HttpResponse(status, _, _, _) ⇒ isCompressible(res) && status.isSuccess
|
||||
}
|
||||
private[coding] def isCompressible(msg: HttpMessage): Boolean =
|
||||
msg.entity.contentType.mediaType.compressible
|
||||
msg.entity.contentType.mediaType.isCompressible
|
||||
|
||||
private[coding] val isContentEncodingHeader: HttpHeader ⇒ Boolean = _.isInstanceOf[`Content-Encoding`]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,7 +56,9 @@ object StrictForm {
|
|||
def unmarshallerFromFSU[T](fsu: FromStringUnmarshaller[T]): FromStrictFormFieldUnmarshaller[T] =
|
||||
Unmarshaller.withMaterializer(implicit ec ⇒ implicit mat ⇒ {
|
||||
case FromString(value) ⇒ fsu(value)
|
||||
case FromPart(value) ⇒ fsu(value.entity.data.decodeString(value.entity.contentType.charset.nioCharset.name))
|
||||
case FromPart(value) ⇒
|
||||
val charsetName = value.entity.contentType.asInstanceOf[ContentType.NonBinary].charset.nioCharset.name
|
||||
fsu(value.entity.data.decodeString(charsetName))
|
||||
})
|
||||
|
||||
@implicitNotFound("In order to unmarshal a `StrictForm.Field` to type `${T}` you need to supply a " +
|
||||
|
|
@ -76,8 +78,10 @@ object StrictForm {
|
|||
implicit def fromFSU[T](implicit fsu: FromStringUnmarshaller[T]) =
|
||||
new FieldUnmarshaller[T] {
|
||||
def unmarshalString(value: String)(implicit ec: ExecutionContext, mat: Materializer) = fsu(value)
|
||||
def unmarshalPart(value: Multipart.FormData.BodyPart.Strict)(implicit ec: ExecutionContext, mat: Materializer) =
|
||||
fsu(value.entity.data.decodeString(value.entity.contentType.charset.nioCharset.name))
|
||||
def unmarshalPart(value: Multipart.FormData.BodyPart.Strict)(implicit ec: ExecutionContext, mat: Materializer) = {
|
||||
val charsetName = value.entity.contentType.asInstanceOf[ContentType.NonBinary].charset.nioCharset.name
|
||||
fsu(value.entity.data.decodeString(charsetName))
|
||||
}
|
||||
}
|
||||
implicit def fromFEU[T](implicit feu: FromEntityUnmarshaller[T]) =
|
||||
new FieldUnmarshaller[T] {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.http.scaladsl.marshalling
|
||||
|
||||
import scala.collection.immutable
|
||||
import akka.http.scaladsl.model._
|
||||
|
||||
sealed trait ContentTypeOverrider[T] {
|
||||
def apply(value: T, newContentType: ContentType): T
|
||||
}
|
||||
|
||||
object ContentTypeOverrider {
|
||||
|
||||
implicit def forEntity[T <: HttpEntity]: ContentTypeOverrider[T] = new ContentTypeOverrider[T] {
|
||||
def apply(value: T, newContentType: ContentType) =
|
||||
value.withContentType(newContentType).asInstanceOf[T] // can't be expressed in types
|
||||
}
|
||||
|
||||
implicit def forHeadersAndEntity[T <: HttpEntity] = new ContentTypeOverrider[(immutable.Seq[HttpHeader], T)] {
|
||||
def apply(value: (immutable.Seq[HttpHeader], T), newContentType: ContentType) =
|
||||
value._1 -> value._2.withContentType(newContentType).asInstanceOf[T]
|
||||
}
|
||||
|
||||
implicit val forResponse = new ContentTypeOverrider[HttpResponse] {
|
||||
def apply(value: HttpResponse, newContentType: ContentType) =
|
||||
value.mapEntity(forEntity(_: ResponseEntity, newContentType))
|
||||
}
|
||||
|
||||
implicit val forRequest = new ContentTypeOverrider[HttpRequest] {
|
||||
def apply(value: HttpRequest, newContentType: ContentType) =
|
||||
value.mapEntity(forEntity(_: RequestEntity, newContentType))
|
||||
}
|
||||
}
|
||||
|
|
@ -5,16 +5,15 @@
|
|||
package akka.http.scaladsl.marshalling
|
||||
|
||||
import scala.concurrent.{ ExecutionContext, Future }
|
||||
import akka.http.scaladsl.model.HttpCharsets._
|
||||
import akka.http.scaladsl.server.ContentNegotiator
|
||||
import akka.http.scaladsl.model._
|
||||
import akka.http.scaladsl.util.FastFuture._
|
||||
|
||||
object Marshal {
|
||||
def apply[T](value: T): Marshal[T] = new Marshal(value)
|
||||
|
||||
case class UnacceptableResponseContentTypeException(supported: Set[ContentType]) extends RuntimeException
|
||||
|
||||
private class MarshallingWeight(val weight: Float, val marshal: () ⇒ HttpResponse)
|
||||
case class UnacceptableResponseContentTypeException(supported: Set[ContentNegotiator.Alternative])
|
||||
extends RuntimeException
|
||||
}
|
||||
|
||||
class Marshal[A](val value: A) {
|
||||
|
|
@ -25,9 +24,9 @@ class Marshal[A](val value: A) {
|
|||
def to[B](implicit m: Marshaller[A, B], ec: ExecutionContext): Future[B] =
|
||||
m(value).fast.map {
|
||||
_.head match {
|
||||
case Marshalling.WithFixedCharset(_, _, marshal) ⇒ marshal()
|
||||
case Marshalling.WithOpenCharset(_, marshal) ⇒ marshal(HttpCharsets.`UTF-8`)
|
||||
case Marshalling.Opaque(marshal) ⇒ marshal()
|
||||
case Marshalling.WithFixedContentType(_, marshal) ⇒ marshal()
|
||||
case Marshalling.WithOpenCharset(_, marshal) ⇒ marshal(HttpCharsets.`UTF-8`)
|
||||
case Marshalling.Opaque(marshal) ⇒ marshal()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -36,40 +35,32 @@ class Marshal[A](val value: A) {
|
|||
*/
|
||||
def toResponseFor(request: HttpRequest)(implicit m: ToResponseMarshaller[A], ec: ExecutionContext): Future[HttpResponse] = {
|
||||
import akka.http.scaladsl.marshalling.Marshal._
|
||||
val mediaRanges = request.acceptedMediaRanges // cache for performance
|
||||
val charsetRanges = request.acceptedCharsetRanges // cache for performance
|
||||
def qValueMT(mediaType: MediaType) = request.qValueForMediaType(mediaType, mediaRanges)
|
||||
def qValueCS(charset: HttpCharset) = request.qValueForCharset(charset, charsetRanges)
|
||||
val ctn = ContentNegotiator(request.headers)
|
||||
|
||||
m(value).fast.map { marshallings ⇒
|
||||
val defaultMarshallingWeight = new MarshallingWeight(0f, { () ⇒
|
||||
val supportedContentTypes = marshallings collect {
|
||||
case Marshalling.WithFixedCharset(mt, cs, _) ⇒ ContentType(mt, cs)
|
||||
case Marshalling.WithOpenCharset(mt, _) ⇒ ContentType(mt)
|
||||
}
|
||||
throw UnacceptableResponseContentTypeException(supportedContentTypes.toSet)
|
||||
})
|
||||
def choose(acc: MarshallingWeight, mt: MediaType, cs: HttpCharset, marshal: () ⇒ HttpResponse) = {
|
||||
val weight = math.min(qValueMT(mt), qValueCS(cs))
|
||||
if (weight > acc.weight) new MarshallingWeight(weight, marshal) else acc
|
||||
}
|
||||
val best = marshallings.foldLeft(defaultMarshallingWeight) {
|
||||
case (acc, Marshalling.WithFixedCharset(mt, cs, marshal)) ⇒
|
||||
choose(acc, mt, cs, marshal)
|
||||
case (acc, Marshalling.WithOpenCharset(mt, marshal)) ⇒
|
||||
def withCharset(cs: HttpCharset) = choose(acc, mt, cs, () ⇒ marshal(cs))
|
||||
// logic for choosing the charset adapted from http://tools.ietf.org/html/rfc7231#section-5.3.3
|
||||
if (qValueCS(`UTF-8`) == 1f) withCharset(`UTF-8`) // prefer UTF-8 if fully accepted
|
||||
else charsetRanges match {
|
||||
// pick the charset which the highest q-value (head of charsetRanges) if it isn't explicitly rejected
|
||||
case (HttpCharsetRange.One(cs, qValue)) +: _ if qValue > 0f ⇒ withCharset(cs)
|
||||
case _ ⇒ acc
|
||||
val supportedAlternatives: List[ContentNegotiator.Alternative] =
|
||||
marshallings.collect {
|
||||
case Marshalling.WithFixedContentType(ct, _) ⇒ ContentNegotiator.Alternative(ct)
|
||||
case Marshalling.WithOpenCharset(mt, _) ⇒ ContentNegotiator.Alternative(mt)
|
||||
}(collection.breakOut)
|
||||
val bestMarshal = {
|
||||
if (supportedAlternatives.nonEmpty) {
|
||||
ctn.pickContentType(supportedAlternatives).flatMap {
|
||||
case best @ (_: ContentType.Binary | _: ContentType.WithFixedCharset) ⇒
|
||||
marshallings collectFirst { case Marshalling.WithFixedContentType(`best`, marshal) ⇒ marshal }
|
||||
case best @ ContentType.WithCharset(bestMT, bestCS) ⇒
|
||||
marshallings collectFirst {
|
||||
case Marshalling.WithFixedContentType(`best`, marshal) ⇒ marshal
|
||||
case Marshalling.WithOpenCharset(`bestMT`, marshal) ⇒ () ⇒ marshal(bestCS)
|
||||
}
|
||||
}
|
||||
|
||||
case (acc, Marshalling.Opaque(marshal)) ⇒
|
||||
if (acc.weight == 0f) new MarshallingWeight(Float.MinPositiveValue, marshal) else acc
|
||||
} else None
|
||||
} orElse {
|
||||
marshallings collectFirst { case Marshalling.Opaque(marshal) ⇒ marshal }
|
||||
} getOrElse {
|
||||
throw UnacceptableResponseContentTypeException(supportedAlternatives.toSet)
|
||||
}
|
||||
best.marshal()
|
||||
bestMarshal()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,39 +19,50 @@ sealed abstract class Marshaller[-A, +B] {
|
|||
|
||||
/**
|
||||
* Reuses this Marshaller's logic to produce a new Marshaller from another type `C` which overrides
|
||||
* the produced [[ContentType]] with another one.
|
||||
* Depending on whether the given [[ContentType]] has a defined charset or not and whether the underlying
|
||||
* marshaller marshals with a fixed charset it can happen, that the wrapping becomes illegal.
|
||||
* For example, a marshaller producing content encoded with UTF-16 cannot be wrapped with a [[ContentType]]
|
||||
* that has a defined charset of UTF-8, since akka-http will never recode entities.
|
||||
* the [[MediaType]] of the marshalling result with the given one.
|
||||
* Note that not all wrappings are legal. f the underlying [[MediaType]] has constraints with regard to the
|
||||
* charsets it allows the new [[MediaType]] must be compatible, since akka-http will never recode entities.
|
||||
* If the wrapping is illegal the [[Future]] produced by the resulting marshaller will contain a [[RuntimeException]].
|
||||
*/
|
||||
def wrap[C, D >: B](contentType: ContentType)(f: C ⇒ A)(implicit mto: MediaTypeOverrider[D]): Marshaller[C, D] =
|
||||
wrapWithEC[C, D](contentType)(_ ⇒ f)
|
||||
def wrap[C, D >: B](newMediaType: MediaType)(f: C ⇒ A)(implicit mto: ContentTypeOverrider[D]): Marshaller[C, D] =
|
||||
wrapWithEC[C, D](newMediaType)(_ ⇒ f)
|
||||
|
||||
/**
|
||||
* Reuses this Marshaller's logic to produce a new Marshaller from another type `C` which overrides
|
||||
* the produced [[ContentType]] with another one.
|
||||
* Depending on whether the given [[ContentType]] has a defined charset or not and whether the underlying
|
||||
* marshaller marshals with a fixed charset it can happen, that the wrapping becomes illegal.
|
||||
* For example, a marshaller producing content encoded with UTF-16 cannot be wrapped with a [[ContentType]]
|
||||
* that has a defined charset of UTF-8, since akka-http will never recode entities.
|
||||
* the [[MediaType]] of the marshalling result with the given one.
|
||||
* Note that not all wrappings are legal. f the underlying [[MediaType]] has constraints with regard to the
|
||||
* charsets it allows the new [[MediaType]] must be compatible, since akka-http will never recode entities.
|
||||
* If the wrapping is illegal the [[Future]] produced by the resulting marshaller will contain a [[RuntimeException]].
|
||||
*/
|
||||
def wrapWithEC[C, D >: B](contentType: ContentType)(f: ExecutionContext ⇒ C ⇒ A)(implicit mto: MediaTypeOverrider[D]): Marshaller[C, D] =
|
||||
def wrapWithEC[C, D >: B](newMediaType: MediaType)(f: ExecutionContext ⇒ C ⇒ A)(implicit cto: ContentTypeOverrider[D]): Marshaller[C, D] =
|
||||
Marshaller { implicit ec ⇒
|
||||
value ⇒
|
||||
import Marshalling._
|
||||
this(f(ec)(value)).fast map {
|
||||
_ map {
|
||||
case WithFixedCharset(_, cs, marshal) if contentType.hasOpenCharset || contentType.charset == cs ⇒
|
||||
WithFixedCharset(contentType.mediaType, cs, () ⇒ mto(marshal(), contentType.mediaType))
|
||||
case WithOpenCharset(_, marshal) if contentType.hasOpenCharset ⇒
|
||||
WithOpenCharset(contentType.mediaType, cs ⇒ mto(marshal(cs), contentType.mediaType))
|
||||
case WithOpenCharset(_, marshal) ⇒
|
||||
WithFixedCharset(contentType.mediaType, contentType.charset, () ⇒ mto(marshal(contentType.charset), contentType.mediaType))
|
||||
case Opaque(marshal) if contentType.definedCharset.isEmpty ⇒ Opaque(() ⇒ mto(marshal(), contentType.mediaType))
|
||||
case x ⇒ sys.error(s"Illegal marshaller wrapping. Marshalling `$x` cannot be wrapped with ContentType `$contentType`")
|
||||
(_, newMediaType) match {
|
||||
case (WithFixedContentType(_, marshal), newMT: MediaType.Binary) ⇒
|
||||
WithFixedContentType(newMT, () ⇒ cto(marshal(), newMT))
|
||||
case (WithFixedContentType(oldCT: ContentType.Binary, marshal), newMT: MediaType.WithFixedCharset) ⇒
|
||||
WithFixedContentType(newMT, () ⇒ cto(marshal(), newMT))
|
||||
case (WithFixedContentType(oldCT: ContentType.NonBinary, marshal), newMT: MediaType.WithFixedCharset) if oldCT.charset == newMT.charset ⇒
|
||||
WithFixedContentType(newMT, () ⇒ cto(marshal(), newMT))
|
||||
case (WithFixedContentType(oldCT: ContentType.NonBinary, marshal), newMT: MediaType.WithOpenCharset) ⇒
|
||||
val newCT = newMT withCharset oldCT.charset
|
||||
WithFixedContentType(newCT, () ⇒ cto(marshal(), newCT))
|
||||
|
||||
case (WithOpenCharset(oldMT, marshal), newMT: MediaType.WithOpenCharset) ⇒
|
||||
WithOpenCharset(newMT, cs ⇒ cto(marshal(cs), newMT withCharset cs))
|
||||
case (WithOpenCharset(oldMT, marshal), newMT: MediaType.WithFixedCharset) ⇒
|
||||
WithFixedContentType(newMT, () ⇒ cto(marshal(newMT.charset), newMT))
|
||||
|
||||
case (Opaque(marshal), newMT: MediaType.Binary) ⇒
|
||||
WithFixedContentType(newMT, () ⇒ cto(marshal(), newMT))
|
||||
case (Opaque(marshal), newMT: MediaType.WithFixedCharset) ⇒
|
||||
WithFixedContentType(newMT, () ⇒ cto(marshal(), newMT))
|
||||
|
||||
case x ⇒ sys.error(s"Illegal marshaller wrapping. Marshalling `$x` cannot be wrapped with MediaType `$newMediaType`")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -103,13 +114,13 @@ object Marshaller
|
|||
/**
|
||||
* Helper for creating a synchronous [[Marshaller]] to content with a fixed charset from the given function.
|
||||
*/
|
||||
def withFixedCharset[A, B](mediaType: MediaType, charset: HttpCharset)(marshal: A ⇒ B): Marshaller[A, B] =
|
||||
strict { value ⇒ Marshalling.WithFixedCharset(mediaType, charset, () ⇒ marshal(value)) }
|
||||
def withFixedContentType[A, B](contentType: ContentType)(marshal: A ⇒ B): Marshaller[A, B] =
|
||||
strict { value ⇒ Marshalling.WithFixedContentType(contentType, () ⇒ marshal(value)) }
|
||||
|
||||
/**
|
||||
* Helper for creating a synchronous [[Marshaller]] to content with a negotiable charset from the given function.
|
||||
*/
|
||||
def withOpenCharset[A, B](mediaType: MediaType)(marshal: (A, HttpCharset) ⇒ B): Marshaller[A, B] =
|
||||
def withOpenCharset[A, B](mediaType: MediaType.WithOpenCharset)(marshal: (A, HttpCharset) ⇒ B): Marshaller[A, B] =
|
||||
strict { value ⇒ Marshalling.WithOpenCharset(mediaType, charset ⇒ marshal(value, charset)) }
|
||||
|
||||
/**
|
||||
|
|
@ -136,19 +147,19 @@ sealed trait Marshalling[+A] {
|
|||
}
|
||||
|
||||
object Marshalling {
|
||||
|
||||
/**
|
||||
* A Marshalling to a specific MediaType and charset.
|
||||
* A Marshalling to a specific [[ContentType]].
|
||||
*/
|
||||
final case class WithFixedCharset[A](mediaType: MediaType,
|
||||
charset: HttpCharset,
|
||||
marshal: () ⇒ A) extends Marshalling[A] {
|
||||
def map[B](f: A ⇒ B): WithFixedCharset[B] = copy(marshal = () ⇒ f(marshal()))
|
||||
final case class WithFixedContentType[A](contentType: ContentType,
|
||||
marshal: () ⇒ A) extends Marshalling[A] {
|
||||
def map[B](f: A ⇒ B): WithFixedContentType[B] = copy(marshal = () ⇒ f(marshal()))
|
||||
}
|
||||
|
||||
/**
|
||||
* A Marshalling to a specific MediaType and a potentially flexible charset.
|
||||
* A Marshalling to a specific [[MediaType]] with a flexible charset.
|
||||
*/
|
||||
final case class WithOpenCharset[A](mediaType: MediaType,
|
||||
final case class WithOpenCharset[A](mediaType: MediaType.WithOpenCharset,
|
||||
marshal: HttpCharset ⇒ A) extends Marshalling[A] {
|
||||
def map[B](f: A ⇒ B): WithOpenCharset[B] = copy(marshal = cs ⇒ f(marshal(cs)))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.http.scaladsl.marshalling
|
||||
|
||||
import scala.collection.immutable
|
||||
import akka.http.scaladsl.model._
|
||||
|
||||
sealed trait MediaTypeOverrider[T] {
|
||||
def apply(value: T, mediaType: MediaType): T
|
||||
}
|
||||
object MediaTypeOverrider {
|
||||
implicit def forEntity[T <: HttpEntity]: MediaTypeOverrider[T] = new MediaTypeOverrider[T] {
|
||||
def apply(value: T, mediaType: MediaType) =
|
||||
value.withContentType(value.contentType withMediaType mediaType).asInstanceOf[T] // can't be expressed in types
|
||||
}
|
||||
implicit def forHeadersAndEntity[T <: HttpEntity] = new MediaTypeOverrider[(immutable.Seq[HttpHeader], T)] {
|
||||
def apply(value: (immutable.Seq[HttpHeader], T), mediaType: MediaType) =
|
||||
value._1 -> value._2.withContentType(value._2.contentType withMediaType mediaType).asInstanceOf[T]
|
||||
}
|
||||
implicit val forResponse = new MediaTypeOverrider[HttpResponse] {
|
||||
def apply(value: HttpResponse, mediaType: MediaType) =
|
||||
value.mapEntity(forEntity(_: ResponseEntity, mediaType))
|
||||
}
|
||||
implicit val forRequest = new MediaTypeOverrider[HttpRequest] {
|
||||
def apply(value: HttpRequest, mediaType: MediaType) =
|
||||
value.mapEntity(forEntity(_: RequestEntity, mediaType))
|
||||
}
|
||||
}
|
||||
|
|
@ -13,8 +13,8 @@ trait MultipartMarshallers {
|
|||
implicit def multipartMarshaller[T <: Multipart](implicit log: LoggingAdapter = NoLogging): ToEntityMarshaller[T] =
|
||||
Marshaller strict { value ⇒
|
||||
val boundary = randomBoundary()
|
||||
val contentType = ContentType(value.mediaType withBoundary boundary)
|
||||
Marshalling.WithOpenCharset(contentType.mediaType, { charset ⇒
|
||||
val mediaType = value.mediaType withBoundary boundary
|
||||
Marshalling.WithOpenCharset(mediaType, { charset ⇒
|
||||
value.toEntity(charset, boundary)(log)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,51 +13,39 @@ import akka.util.ByteString
|
|||
trait PredefinedToEntityMarshallers extends MultipartMarshallers {
|
||||
|
||||
implicit val ByteArrayMarshaller: ToEntityMarshaller[Array[Byte]] = byteArrayMarshaller(`application/octet-stream`)
|
||||
def byteArrayMarshaller(mediaType: MediaType, charset: HttpCharset): ToEntityMarshaller[Array[Byte]] = {
|
||||
val ct = ContentType(mediaType, charset)
|
||||
Marshaller.withFixedCharset(ct.mediaType, ct.definedCharset.get) { bytes ⇒ HttpEntity(ct, bytes) }
|
||||
}
|
||||
def byteArrayMarshaller(mediaType: MediaType): ToEntityMarshaller[Array[Byte]] = {
|
||||
val ct = ContentType(mediaType)
|
||||
// since we don't want to recode we simply ignore the charset determined by content negotiation here
|
||||
Marshaller.withOpenCharset(ct.mediaType) { (bytes, _) ⇒ HttpEntity(ct, bytes) }
|
||||
}
|
||||
def byteArrayMarshaller(contentType: ContentType): ToEntityMarshaller[Array[Byte]] =
|
||||
Marshaller.withFixedContentType(contentType) { bytes ⇒ HttpEntity(contentType, bytes) }
|
||||
|
||||
implicit val ByteStringMarshaller: ToEntityMarshaller[ByteString] = byteStringMarshaller(`application/octet-stream`)
|
||||
def byteStringMarshaller(mediaType: MediaType, charset: HttpCharset): ToEntityMarshaller[ByteString] = {
|
||||
val ct = ContentType(mediaType, charset)
|
||||
Marshaller.withFixedCharset(ct.mediaType, ct.definedCharset.get) { bytes ⇒ HttpEntity(ct, bytes) }
|
||||
}
|
||||
def byteStringMarshaller(mediaType: MediaType): ToEntityMarshaller[ByteString] = {
|
||||
val ct = ContentType(mediaType)
|
||||
// since we don't want to recode we simply ignore the charset determined by content negotiation here
|
||||
Marshaller.withOpenCharset(ct.mediaType) { (bytes, _) ⇒ HttpEntity(ct, bytes) }
|
||||
}
|
||||
def byteStringMarshaller(contentType: ContentType): ToEntityMarshaller[ByteString] =
|
||||
Marshaller.withFixedContentType(contentType) { bytes ⇒ HttpEntity(contentType, bytes) }
|
||||
|
||||
implicit val CharArrayMarshaller: ToEntityMarshaller[Array[Char]] = charArrayMarshaller(`text/plain`)
|
||||
def charArrayMarshaller(mediaType: MediaType): ToEntityMarshaller[Array[Char]] =
|
||||
Marshaller.withOpenCharset(mediaType) { (value, charset) ⇒
|
||||
if (value.length > 0) {
|
||||
val charBuffer = CharBuffer.wrap(value)
|
||||
val byteBuffer = charset.nioCharset.encode(charBuffer)
|
||||
val array = new Array[Byte](byteBuffer.remaining())
|
||||
byteBuffer.get(array)
|
||||
HttpEntity(ContentType(mediaType, charset), array)
|
||||
} else HttpEntity.Empty
|
||||
}
|
||||
def charArrayMarshaller(mediaType: MediaType.WithOpenCharset): ToEntityMarshaller[Array[Char]] =
|
||||
Marshaller.withOpenCharset(mediaType) { (value, charset) ⇒ marshalCharArray(value, mediaType withCharset charset) }
|
||||
def charArrayMarshaller(mediaType: MediaType.WithFixedCharset): ToEntityMarshaller[Array[Char]] =
|
||||
Marshaller.withFixedContentType(mediaType) { value ⇒ marshalCharArray(value, mediaType) }
|
||||
|
||||
private def marshalCharArray(value: Array[Char], contentType: ContentType.NonBinary): HttpEntity.Strict =
|
||||
if (value.length > 0) {
|
||||
val charBuffer = CharBuffer.wrap(value)
|
||||
val byteBuffer = contentType.charset.nioCharset.encode(charBuffer)
|
||||
val array = new Array[Byte](byteBuffer.remaining())
|
||||
byteBuffer.get(array)
|
||||
HttpEntity(contentType, array)
|
||||
} else HttpEntity.Empty
|
||||
|
||||
implicit val StringMarshaller: ToEntityMarshaller[String] = stringMarshaller(`text/plain`)
|
||||
def stringMarshaller(mediaType: MediaType): ToEntityMarshaller[String] =
|
||||
Marshaller.withOpenCharset(mediaType) { (s, cs) ⇒ HttpEntity(ContentType(mediaType, cs), s) }
|
||||
def stringMarshaller(mediaType: MediaType.WithOpenCharset): ToEntityMarshaller[String] =
|
||||
Marshaller.withOpenCharset(mediaType) { (s, cs) ⇒ HttpEntity(mediaType withCharset cs, s) }
|
||||
def stringMarshaller(mediaType: MediaType.WithFixedCharset): ToEntityMarshaller[String] =
|
||||
Marshaller.withFixedContentType(mediaType) { s ⇒ HttpEntity(mediaType, s) }
|
||||
|
||||
implicit val FormDataMarshaller: ToEntityMarshaller[FormData] =
|
||||
Marshaller.withOpenCharset(`application/x-www-form-urlencoded`) { (formData, charset) ⇒
|
||||
formData.toEntity(charset)
|
||||
}
|
||||
Marshaller.withOpenCharset(`application/x-www-form-urlencoded`) { _ toEntity _ }
|
||||
|
||||
implicit val MessageEntityMarshaller: ToEntityMarshaller[MessageEntity] = Marshaller strict { value ⇒
|
||||
Marshalling.WithFixedCharset(value.contentType.mediaType, value.contentType.charset, () ⇒ value)
|
||||
}
|
||||
implicit val MessageEntityMarshaller: ToEntityMarshaller[MessageEntity] =
|
||||
Marshaller strict { value ⇒ Marshalling.WithFixedContentType(value.contentType, () ⇒ value) }
|
||||
}
|
||||
|
||||
object PredefinedToEntityMarshallers extends PredefinedToEntityMarshallers
|
||||
|
|
|
|||
|
|
@ -0,0 +1,264 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.http.scaladsl.server
|
||||
|
||||
import akka.http.scaladsl.model
|
||||
import akka.http.scaladsl.model._
|
||||
import akka.http.scaladsl.model.headers._
|
||||
import HttpCharsets.`UTF-8`
|
||||
|
||||
final class MediaTypeNegotiator(requestHeaders: Seq[HttpHeader]) {
|
||||
|
||||
/**
|
||||
* The media-ranges accepted by the client according to the given request headers, sorted by
|
||||
* 1. increasing generality (i.e. most specific first)
|
||||
* 2. decreasing q-value (only for ranges targeting a single MediaType)
|
||||
* 3. order of appearance in the `Accept` header(s)
|
||||
*/
|
||||
val acceptedMediaRanges: List[MediaRange] =
|
||||
(for {
|
||||
Accept(mediaRanges) ← requestHeaders
|
||||
range ← mediaRanges
|
||||
} yield range).sortBy { // `sortBy` is stable, i.e. upholds the original order on identical keys
|
||||
case x if x.isWildcard ⇒ 2f // most general, needs to come last
|
||||
case MediaRange.One(_, qv) ⇒ -qv // most specific, needs to come first
|
||||
case _ ⇒ 1f // simple range like `image/*`
|
||||
}.toList
|
||||
|
||||
/**
|
||||
* Returns the q-value that the client (implicitly or explicitly) attaches to the given media-type.
|
||||
* See http://tools.ietf.org/html/rfc7231#section-5.3.1 for details.
|
||||
*/
|
||||
def qValueFor(mediaType: MediaType): Float =
|
||||
acceptedMediaRanges match {
|
||||
case Nil ⇒ 1.0f
|
||||
case x ⇒ x collectFirst { case r if r matches mediaType ⇒ r.qValue } getOrElse 0f
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the given [[MediaType]] is accepted by the client.
|
||||
*/
|
||||
def isAccepted(mediaType: MediaType): Boolean = qValueFor(mediaType) > 0f
|
||||
}
|
||||
|
||||
final class CharsetNegotiator(requestHeaders: Seq[HttpHeader]) {
|
||||
|
||||
/**
|
||||
* The charset-ranges accepted by the client according to given request headers, sorted by
|
||||
* 1. increasing generality (i.e. most specific first)
|
||||
* 2. decreasing q-value (only for ranges targeting a single HttpCharset)
|
||||
* 3. order of appearance in the `Accept-Charset` header(s)
|
||||
*/
|
||||
val acceptedCharsetRanges: List[HttpCharsetRange] =
|
||||
(for {
|
||||
`Accept-Charset`(charsetRanges) ← requestHeaders
|
||||
range ← charsetRanges
|
||||
} yield range).sortBy { // `sortBy` is stable, i.e. upholds the original order on identical keys
|
||||
case _: HttpCharsetRange.`*` ⇒ 1f // most general, needs to come last
|
||||
case x ⇒ -x.qValue // all others come first
|
||||
}.toList
|
||||
|
||||
/**
|
||||
* Returns the q-value that the client (implicitly or explicitly) attaches to the given charset.
|
||||
* See http://tools.ietf.org/html/rfc7231#section-5.3.1 for details.
|
||||
*/
|
||||
def qValueFor(charset: HttpCharset): Float =
|
||||
acceptedCharsetRanges match {
|
||||
case Nil ⇒ 1.0f
|
||||
case x ⇒ x collectFirst { case r if r matches charset ⇒ r.qValue } getOrElse 0f
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the given charset is accepted by the client.
|
||||
*/
|
||||
def isAccepted(charset: HttpCharset): Boolean = qValueFor(charset) > 0f
|
||||
|
||||
/**
|
||||
* Picks the charset that is most preferred by the client with a bias towards UTF-8,
|
||||
* i.e. if the client accepts all charsets with equal preference then UTF-8 is picked.
|
||||
* If the client doesn't accept any charsets the method returns `None`.
|
||||
*
|
||||
* See also: http://tools.ietf.org/html/rfc7231#section-5.3.3
|
||||
*/
|
||||
def pickBest: Option[HttpCharset] =
|
||||
acceptedCharsetRanges match {
|
||||
case Nil ⇒ Some(`UTF-8`)
|
||||
case HttpCharsetRange.One(cs, _) :: _ ⇒ Some(cs)
|
||||
case HttpCharsetRange.`*`(qv) :: _ if qv > 0f ⇒ Some(`UTF-8`)
|
||||
case _ ⇒ None
|
||||
}
|
||||
}
|
||||
|
||||
final class ContentNegotiator(requestHeaders: Seq[HttpHeader]) {
|
||||
import ContentNegotiator.Alternative
|
||||
|
||||
val mtn = new MediaTypeNegotiator(requestHeaders)
|
||||
val csn = new CharsetNegotiator(requestHeaders)
|
||||
|
||||
def qValueFor(alternative: Alternative): Float =
|
||||
alternative match {
|
||||
case Alternative.ContentType(ct: ContentType.NonBinary) ⇒
|
||||
math.min(mtn.qValueFor(ct.mediaType), csn.qValueFor(ct.charset))
|
||||
case x ⇒ mtn.qValueFor(x.mediaType)
|
||||
}
|
||||
|
||||
/**
|
||||
* Picks the best of the given content alternatives given the preferences
|
||||
* the client indicated in the request's `Accept` and `Accept-Charset` headers.
|
||||
* See http://tools.ietf.org/html/rfc7231#section-5.3.2 ff for details on the negotiation logic.
|
||||
*
|
||||
* If there are several best alternatives that the client has equal preference for
|
||||
* the order of the given alternatives is used as a tie breaker (first one wins).
|
||||
*
|
||||
* If none of the given alternatives is acceptable to the client the methods return `None`.
|
||||
*/
|
||||
def pickContentType(alternatives: List[Alternative]): Option[ContentType] =
|
||||
alternatives
|
||||
.map(alt ⇒ alt → qValueFor(alt))
|
||||
.sortBy(-_._2)
|
||||
.collectFirst { case (alt, q) if q > 0f ⇒ alt }
|
||||
.flatMap {
|
||||
case Alternative.ContentType(ct) ⇒ Some(ct)
|
||||
case Alternative.MediaType(mt) ⇒ csn.pickBest.map(mt.withCharset)
|
||||
}
|
||||
}
|
||||
|
||||
object ContentNegotiator {
|
||||
sealed trait Alternative {
|
||||
def mediaType: MediaType
|
||||
def format: String
|
||||
}
|
||||
object Alternative {
|
||||
implicit def apply(contentType: model.ContentType): ContentType = ContentType(contentType)
|
||||
implicit def apply(mediaType: model.MediaType): Alternative =
|
||||
mediaType match {
|
||||
case x: model.MediaType.Binary ⇒ ContentType(x)
|
||||
case x: model.MediaType.WithFixedCharset ⇒ ContentType(x)
|
||||
case x: model.MediaType.WithOpenCharset ⇒ MediaType(x)
|
||||
}
|
||||
|
||||
case class ContentType(contentType: model.ContentType) extends Alternative {
|
||||
def mediaType = contentType.mediaType
|
||||
def format = contentType.toString
|
||||
}
|
||||
case class MediaType(mediaType: model.MediaType.WithOpenCharset) extends Alternative {
|
||||
def format = mediaType.toString
|
||||
}
|
||||
}
|
||||
|
||||
def apply(requestHeaders: Seq[HttpHeader]) = new ContentNegotiator(requestHeaders)
|
||||
}
|
||||
|
||||
final class EncodingNegotiator(requestHeaders: Seq[HttpHeader]) {
|
||||
|
||||
/**
|
||||
* The encoding-ranges accepted by the client according to given request headers, sorted by
|
||||
* 1. increasing generality (i.e. most specific first)
|
||||
* 2. decreasing q-value (only for ranges targeting a single HttpEncoding)
|
||||
* 3. order of appearance in the `Accept-Encoding` header(s)
|
||||
*/
|
||||
val acceptedEncodingRanges: List[HttpEncodingRange] =
|
||||
(for {
|
||||
`Accept-Encoding`(encodingRanges) ← requestHeaders
|
||||
range ← encodingRanges
|
||||
} yield range).sortBy { // `sortBy` is stable, i.e. upholds the original order on identical keys
|
||||
case _: HttpEncodingRange.`*` ⇒ 1f // most general, needs to come last
|
||||
case x ⇒ -x.qValue // all others come first
|
||||
}.toList
|
||||
|
||||
/**
|
||||
* Returns the q-value that the client (implicitly or explicitly) attaches to the given encoding.
|
||||
* See http://tools.ietf.org/html/rfc7231#section-5.3.1 for details.
|
||||
*/
|
||||
def qValueFor(encoding: HttpEncoding): Float =
|
||||
acceptedEncodingRanges match {
|
||||
case Nil ⇒ 1.0f
|
||||
case x ⇒ x collectFirst { case r if r matches encoding ⇒ r.qValue } getOrElse 0f
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the given encoding is accepted by the client.
|
||||
*/
|
||||
def isAccepted(encoding: HttpEncoding): Boolean = qValueFor(encoding) > 0f
|
||||
|
||||
/**
|
||||
* Determines whether the request has an `Accept-Encoding` clause matching the given encoding.
|
||||
*/
|
||||
def hasMatchingFor(encoding: HttpEncoding): Boolean =
|
||||
acceptedEncodingRanges.exists(_ matches encoding)
|
||||
|
||||
/**
|
||||
* Picks the best of the given encoding alternatives given the preferences
|
||||
* the client indicated in the request's `Accept-Encoding` headers.
|
||||
* See http://tools.ietf.org/html/rfc7231#section-5.3.4 for details on the negotiation logic.
|
||||
*
|
||||
* If there are several best encoding alternatives that the client has equal preference for
|
||||
* the order of the given alternatives is used as a tie breaker (first one wins).
|
||||
*
|
||||
* If none of the given alternatives is acceptable to the client the methods return `None`.
|
||||
*/
|
||||
def pickEncoding(alternatives: List[HttpEncoding]): Option[HttpEncoding] =
|
||||
alternatives
|
||||
.map(alt ⇒ alt → qValueFor(alt))
|
||||
.sortBy(-_._2)
|
||||
.collectFirst { case (alt, q) if q > 0f ⇒ alt }
|
||||
}
|
||||
|
||||
object EncodingNegotiator {
|
||||
def apply(requestHeaders: Seq[HttpHeader]) = new EncodingNegotiator(requestHeaders)
|
||||
}
|
||||
|
||||
final class LanguageNegotiator(requestHeaders: Seq[HttpHeader]) {
|
||||
|
||||
/**
|
||||
* The language-ranges accepted by the client according to given request headers, sorted by
|
||||
* 1. increasing generality (i.e. most specific first)
|
||||
* 2. decreasing q-value (only for ranges targeting a single Language)
|
||||
* 3. order of appearance in the `Accept-Language` header(s)
|
||||
*/
|
||||
val acceptedLanguageRanges: List[LanguageRange] =
|
||||
(for {
|
||||
`Accept-Language`(languageRanges) ← requestHeaders
|
||||
range ← languageRanges
|
||||
} yield range).sortBy { // `sortBy` is stable, i.e. upholds the original order on identical keys
|
||||
case _: LanguageRange.`*` ⇒ 1f // most general, needs to come last
|
||||
case x ⇒ -(2 * x.subTags.size + x.qValue) // more subtags -> more specific -> go first
|
||||
}.toList
|
||||
|
||||
/**
|
||||
* Returns the q-value that the client (implicitly or explicitly) attaches to the given language.
|
||||
* See http://tools.ietf.org/html/rfc7231#section-5.3.1 for details.
|
||||
*/
|
||||
def qValueFor(language: Language): Float =
|
||||
acceptedLanguageRanges match {
|
||||
case Nil ⇒ 1.0f
|
||||
case x ⇒ x collectFirst { case r if r matches language ⇒ r.qValue } getOrElse 0f
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the given language is accepted by the client.
|
||||
*/
|
||||
def isAccepted(language: Language): Boolean = qValueFor(language) > 0f
|
||||
|
||||
/**
|
||||
* Picks the best of the given language alternatives given the preferences
|
||||
* the client indicated in the request's `Accept-Language` headers.
|
||||
* See http://tools.ietf.org/html/rfc7231#section-5.3.5 for details on the negotiation logic.
|
||||
*
|
||||
* If there are several best language alternatives that the client has equal preference for
|
||||
* the order of the given alternatives is used as a tie breaker (first one wins).
|
||||
*
|
||||
* If none of the given alternatives is acceptable to the client the methods return `None`.
|
||||
*/
|
||||
def pickLanguage(alternatives: List[Language]): Option[Language] =
|
||||
alternatives
|
||||
.map(alt ⇒ alt → qValueFor(alt))
|
||||
.sortBy(-_._2)
|
||||
.collectFirst { case (alt, q) if q > 0f ⇒ alt }
|
||||
}
|
||||
|
||||
object LanguageNegotiator {
|
||||
def apply(requestHeaders: Seq[HttpHeader]) = new LanguageNegotiator(requestHeaders)
|
||||
}
|
||||
|
|
@ -112,7 +112,7 @@ case object RequestEntityExpectedRejection extends Rejection
|
|||
* Signals that the request was rejected because the service is not capable of producing a response entity whose
|
||||
* content type is accepted by the client
|
||||
*/
|
||||
case class UnacceptedResponseContentTypeRejection(supported: immutable.Set[ContentType]) extends Rejection
|
||||
case class UnacceptedResponseContentTypeRejection(supported: immutable.Set[ContentNegotiator.Alternative]) extends Rejection
|
||||
|
||||
/**
|
||||
* Rejection created by encoding filters.
|
||||
|
|
|
|||
|
|
@ -188,8 +188,8 @@ object RejectionHandler {
|
|||
}
|
||||
.handleAll[UnacceptedResponseContentTypeRejection] { rejections ⇒
|
||||
val supported = rejections.flatMap(_.supported)
|
||||
complete((NotAcceptable, "Resource representation is only available with these Content-Types:\n" +
|
||||
supported.map(_.value).mkString("\n")))
|
||||
val msg = supported.map(_.format).mkString("Resource representation is only available with these types:\n", "\n", "")
|
||||
complete((NotAcceptable, msg))
|
||||
}
|
||||
.handleAll[UnacceptedResponseEncodingRejection] { rejections ⇒
|
||||
val supported = rejections.flatMap(_.supported)
|
||||
|
|
|
|||
|
|
@ -5,10 +5,9 @@
|
|||
package akka.http.scaladsl.server
|
||||
package directives
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.collection.immutable
|
||||
import scala.util.control.NonFatal
|
||||
import akka.http.scaladsl.model.headers.{ HttpEncodings, `Accept-Encoding`, HttpEncoding, HttpEncodingRange }
|
||||
import akka.http.scaladsl.model.headers.{ HttpEncodings, HttpEncoding }
|
||||
import akka.http.scaladsl.model._
|
||||
import akka.http.scaladsl.coding._
|
||||
import akka.http.impl.util._
|
||||
|
|
@ -26,8 +25,10 @@ trait CodingDirectives {
|
|||
* if the given response encoding is not accepted by the client.
|
||||
*/
|
||||
def responseEncodingAccepted(encoding: HttpEncoding): Directive0 =
|
||||
extract(_.request.isEncodingAccepted(encoding))
|
||||
.flatMap(if (_) pass else reject(UnacceptedResponseEncodingRejection(Set(encoding))))
|
||||
extractRequest.flatMap { request ⇒
|
||||
if (EncodingNegotiator(request.headers).isAccepted(encoding)) pass
|
||||
else reject(UnacceptedResponseEncodingRejection(Set(encoding)))
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the response with the encoding that is requested by the client via the `Accept-
|
||||
|
|
@ -107,6 +108,16 @@ trait CodingDirectives {
|
|||
*/
|
||||
def decodeRequest: Directive0 =
|
||||
decodeRequestWith(DefaultCoders: _*)
|
||||
|
||||
/**
|
||||
* Inspects the response entity and adds a `Content-Encoding: gzip` response header if
|
||||
* the entities media-type is precompressed with gzip and no `Content-Encoding` header is present yet.
|
||||
*/
|
||||
def withPrecompressedMediaTypeSupport: Directive0 =
|
||||
mapResponse { response ⇒
|
||||
if (response.entity.contentType.mediaType.comp != MediaType.Gzipped) response
|
||||
else response.withDefaultHeaders(headers.`Content-Encoding`(HttpEncodings.gzip))
|
||||
}
|
||||
}
|
||||
|
||||
object CodingDirectives extends CodingDirectives {
|
||||
|
|
@ -117,37 +128,18 @@ object CodingDirectives extends CodingDirectives {
|
|||
def theseOrDefault[T >: Coder](these: Seq[T]): Seq[T] = if (these.isEmpty) DefaultCoders else these
|
||||
|
||||
import BasicDirectives._
|
||||
import HeaderDirectives._
|
||||
import RouteDirectives._
|
||||
|
||||
private def _encodeResponse(encoders: immutable.Seq[Encoder]): Directive0 =
|
||||
optionalHeaderValueByType(classOf[`Accept-Encoding`]) flatMap { accept ⇒
|
||||
val acceptedEncoder = accept match {
|
||||
case None ⇒
|
||||
// use first defined encoder when Accept-Encoding is missing
|
||||
encoders.headOption
|
||||
case Some(`Accept-Encoding`(encodings)) ⇒
|
||||
// provide fallback to identity
|
||||
val withIdentity =
|
||||
if (encodings.exists {
|
||||
case HttpEncodingRange.One(HttpEncodings.identity, _) ⇒ true
|
||||
case _ ⇒ false
|
||||
}) encodings
|
||||
else encodings :+ HttpEncodings.`identity;q=MIN`
|
||||
// sort client-accepted encodings by q-Value (and orig. order) and find first matching encoder
|
||||
@tailrec def find(encodings: List[HttpEncodingRange]): Option[Encoder] = encodings match {
|
||||
case encoding :: rest ⇒
|
||||
encoders.find(e ⇒ encoding.matches(e.encoding)) match {
|
||||
case None ⇒ find(rest)
|
||||
case x ⇒ x
|
||||
}
|
||||
case _ ⇒ None
|
||||
}
|
||||
find(withIdentity.sortBy(e ⇒ (-e.qValue, withIdentity.indexOf(e))).toList)
|
||||
}
|
||||
acceptedEncoder match {
|
||||
BasicDirectives.extractRequest.flatMap { request ⇒
|
||||
val negotiator = EncodingNegotiator(request.headers)
|
||||
val encodings: List[HttpEncoding] = encoders.map(_.encoding)(collection.breakOut)
|
||||
val bestEncoder = negotiator.pickEncoding(encodings).flatMap(be ⇒ encoders.find(_.encoding == be))
|
||||
bestEncoder match {
|
||||
case Some(encoder) ⇒ mapResponse(encoder.encode(_))
|
||||
case _ ⇒ reject(UnacceptedResponseEncodingRejection(encoders.map(_.encoding).toSet))
|
||||
case _ ⇒
|
||||
if (encoders.contains(NoCoding) && !negotiator.hasMatchingFor(HttpEncodings.identity)) pass
|
||||
else reject(UnacceptedResponseEncodingRejection(encodings.toSet))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ trait FileAndResourceDirectives {
|
|||
import RouteDirectives._
|
||||
import BasicDirectives._
|
||||
import RouteConcatenation._
|
||||
import RangeDirectives._
|
||||
|
||||
/**
|
||||
* Completes GET requests with the content of the given file.
|
||||
|
|
@ -51,12 +50,10 @@ trait FileAndResourceDirectives {
|
|||
if (file.isFile && file.canRead)
|
||||
conditionalFor(file.length, file.lastModified) {
|
||||
if (file.length > 0) {
|
||||
withRangeSupport {
|
||||
extractSettings { settings ⇒
|
||||
complete {
|
||||
HttpEntity.Default(contentType, file.length,
|
||||
Source.file(file).withAttributes(ActorAttributes.dispatcher(settings.fileIODispatcher)))
|
||||
}
|
||||
withRangeSupportAndPrecompressedMediaTypeSupportAndExtractSettings { settings ⇒
|
||||
complete {
|
||||
HttpEntity.Default(contentType, file.length,
|
||||
Source.file(file).withAttributes(ActorAttributes.dispatcher(settings.fileIODispatcher)))
|
||||
}
|
||||
}
|
||||
} else complete(HttpEntity.Empty)
|
||||
|
|
@ -90,13 +87,11 @@ trait FileAndResourceDirectives {
|
|||
case Some(ResourceFile(url, length, lastModified)) ⇒
|
||||
conditionalFor(length, lastModified) {
|
||||
if (length > 0) {
|
||||
withRangeSupport {
|
||||
extractSettings { settings ⇒
|
||||
complete {
|
||||
HttpEntity.Default(contentType, length,
|
||||
Source.inputStream(() ⇒ url.openStream())
|
||||
.withAttributes(ActorAttributes.dispatcher(settings.fileIODispatcher)))
|
||||
}
|
||||
withRangeSupportAndPrecompressedMediaTypeSupportAndExtractSettings { settings ⇒
|
||||
complete {
|
||||
HttpEntity.Default(contentType, length,
|
||||
Source.inputStream(() ⇒ url.openStream())
|
||||
.withAttributes(ActorAttributes.dispatcher(settings.fileIODispatcher)))
|
||||
}
|
||||
}
|
||||
} else complete(HttpEntity.Empty)
|
||||
|
|
@ -186,6 +181,11 @@ trait FileAndResourceDirectives {
|
|||
}
|
||||
|
||||
object FileAndResourceDirectives extends FileAndResourceDirectives {
|
||||
private val withRangeSupportAndPrecompressedMediaTypeSupportAndExtractSettings =
|
||||
RangeDirectives.withRangeSupport &
|
||||
CodingDirectives.withPrecompressedMediaTypeSupport &
|
||||
BasicDirectives.extractSettings
|
||||
|
||||
private def withTrailingSlash(path: String): String = if (path endsWith "/") path else path + '/'
|
||||
private def fileSystemPath(base: String, path: Uri.Path, log: LoggingAdapter, separator: Char = File.separatorChar): String = {
|
||||
import java.lang.StringBuilder
|
||||
|
|
@ -260,12 +260,17 @@ object ContentTypeResolver {
|
|||
def withDefaultCharset(charset: HttpCharset): ContentTypeResolver =
|
||||
new ContentTypeResolver {
|
||||
def apply(fileName: String) = {
|
||||
val ext = fileName.lastIndexOf('.') match {
|
||||
case -1 ⇒ ""
|
||||
case x ⇒ fileName.substring(x + 1)
|
||||
}
|
||||
val mediaType = MediaTypes.forExtension(ext) getOrElse MediaTypes.`application/octet-stream`
|
||||
ContentType(mediaType) withDefaultCharset charset
|
||||
val lastDotIx = fileName.lastIndexOf('.')
|
||||
val mediaType = if (lastDotIx >= 0) {
|
||||
fileName.substring(lastDotIx + 1) match {
|
||||
case "gz" ⇒ fileName.lastIndexOf('.', lastDotIx - 1) match {
|
||||
case -1 ⇒ MediaTypes.`application/octet-stream`
|
||||
case x ⇒ MediaTypes.forExtension(fileName.substring(x + 1, lastDotIx)).withComp(MediaType.Gzipped)
|
||||
}
|
||||
case ext ⇒ MediaTypes.forExtension(ext)
|
||||
}
|
||||
} else MediaTypes.`application/octet-stream`
|
||||
ContentType(mediaType, () ⇒ charset)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -51,19 +51,10 @@ trait MiscDirectives {
|
|||
* has equal preference for (even if this preference is zero!)
|
||||
* the order of the arguments is used as a tie breaker (First one wins).
|
||||
*/
|
||||
def selectPreferredLanguage(first: Language, more: Language*): Directive1[Language] = {
|
||||
val available = first :: List(more: _*) // we use List rather than Seq since element count is likely very small
|
||||
BasicDirectives.extractRequest.map { req ⇒
|
||||
val sortedWithQValues = available.zip(available.map(req.qValueForLanguage(_))).sortBy(-_._2)
|
||||
val firstBest = sortedWithQValues.head
|
||||
val moreBest = sortedWithQValues.tail.takeWhile(_._2 == firstBest._2)
|
||||
if (moreBest.nonEmpty) {
|
||||
// we have several languages that have the same qvalue, so we pick the one the `Accept-Header` lists first
|
||||
val allBest = firstBest :: moreBest
|
||||
req.acceptedLanguageRanges.flatMap(range ⇒ allBest.find(t ⇒ range.matches(t._1))).head._1
|
||||
} else firstBest._1 // we have a single best match, so pick that
|
||||
def selectPreferredLanguage(first: Language, more: Language*): Directive1[Language] =
|
||||
BasicDirectives.extractRequest.map { request ⇒
|
||||
LanguageNegotiator(request.headers).pickLanguage(first :: List(more: _*)) getOrElse first
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object MiscDirectives extends MiscDirectives {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ trait RouteDirectives {
|
|||
headers = headers.Location(uri) :: Nil,
|
||||
entity = redirectionType.htmlTemplate match {
|
||||
case "" ⇒ HttpEntity.Empty
|
||||
case template ⇒ HttpEntity(MediaTypes.`text/html`, template format uri)
|
||||
case template ⇒ HttpEntity(ContentTypes.`text/html(UTF-8)`, template format uri)
|
||||
})
|
||||
}
|
||||
//#
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ trait MultipartUnmarshallers {
|
|||
def multipartGeneralUnmarshaller(defaultCharset: HttpCharset)(implicit log: LoggingAdapter = NoLogging): FromEntityUnmarshaller[Multipart.General] =
|
||||
multipartUnmarshaller[Multipart.General, Multipart.General.BodyPart, Multipart.General.BodyPart.Strict](
|
||||
mediaRange = `multipart/*`,
|
||||
defaultContentType = ContentTypes.`text/plain` withCharset defaultCharset,
|
||||
defaultContentType = MediaTypes.`text/plain` withCharset defaultCharset,
|
||||
createBodyPart = Multipart.General.BodyPart(_, _),
|
||||
createStreamed = Multipart.General(_, _),
|
||||
createStrictBodyPart = Multipart.General.BodyPart.Strict,
|
||||
|
|
@ -45,7 +45,7 @@ trait MultipartUnmarshallers {
|
|||
def multipartByteRangesUnmarshaller(defaultCharset: HttpCharset)(implicit log: LoggingAdapter = NoLogging): FromEntityUnmarshaller[Multipart.ByteRanges] =
|
||||
multipartUnmarshaller[Multipart.ByteRanges, Multipart.ByteRanges.BodyPart, Multipart.ByteRanges.BodyPart.Strict](
|
||||
mediaRange = `multipart/byteranges`,
|
||||
defaultContentType = ContentTypes.`text/plain` withCharset defaultCharset,
|
||||
defaultContentType = MediaTypes.`text/plain` withCharset defaultCharset,
|
||||
createBodyPart = (entity, headers) ⇒ Multipart.General.BodyPart(entity, headers).toByteRangesBodyPart.get,
|
||||
createStreamed = (_, parts) ⇒ Multipart.ByteRanges(parts),
|
||||
createStrictBodyPart = (entity, headers) ⇒ Multipart.General.BodyPart.Strict(entity, headers).toByteRangesBodyPart.get,
|
||||
|
|
@ -54,9 +54,9 @@ trait MultipartUnmarshallers {
|
|||
def multipartUnmarshaller[T <: Multipart, BP <: Multipart.BodyPart, BPS <: Multipart.BodyPart.Strict](mediaRange: MediaRange,
|
||||
defaultContentType: ContentType,
|
||||
createBodyPart: (BodyPartEntity, List[HttpHeader]) ⇒ BP,
|
||||
createStreamed: (MultipartMediaType, Source[BP, Any]) ⇒ T,
|
||||
createStreamed: (MediaType.Multipart, Source[BP, Any]) ⇒ T,
|
||||
createStrictBodyPart: (HttpEntity.Strict, List[HttpHeader]) ⇒ BPS,
|
||||
createStrict: (MultipartMediaType, immutable.Seq[BPS]) ⇒ T)(implicit log: LoggingAdapter = NoLogging): FromEntityUnmarshaller[T] =
|
||||
createStrict: (MediaType.Multipart, immutable.Seq[BPS]) ⇒ T)(implicit log: LoggingAdapter = NoLogging): FromEntityUnmarshaller[T] =
|
||||
Unmarshaller { implicit ec ⇒
|
||||
entity ⇒
|
||||
if (entity.contentType.mediaType.isMultipart && mediaRange.matches(entity.contentType.mediaType)) {
|
||||
|
|
@ -68,7 +68,7 @@ trait MultipartUnmarshallers {
|
|||
val parser = new BodyPartParser(defaultContentType, boundary, log)
|
||||
FastFuture.successful {
|
||||
entity match {
|
||||
case HttpEntity.Strict(ContentType(mediaType: MultipartMediaType, _), data) ⇒
|
||||
case HttpEntity.Strict(ContentType(mediaType: MediaType.Multipart, _), data) ⇒
|
||||
val builder = new VectorBuilder[BPS]()
|
||||
val iter = new IteratorInterpreter[ByteString, BodyPartParser.Output](
|
||||
Iterator.single(data), List(parser)).iterator
|
||||
|
|
@ -93,7 +93,7 @@ trait MultipartUnmarshallers {
|
|||
case (BodyPartStart(headers, createEntity), entityParts) ⇒ createBodyPart(createEntity(entityParts), headers)
|
||||
case (ParseError(errorInfo), _) ⇒ throw ParsingException(errorInfo)
|
||||
}
|
||||
createStreamed(entity.contentType.mediaType.asInstanceOf[MultipartMediaType], bodyParts)
|
||||
createStreamed(entity.contentType.mediaType.asInstanceOf[MediaType.Multipart], bodyParts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,29 +21,32 @@ trait PredefinedFromEntityUnmarshallers extends MultipartUnmarshallers {
|
|||
|
||||
implicit def charArrayUnmarshaller: FromEntityUnmarshaller[Array[Char]] =
|
||||
byteStringUnmarshaller mapWithInput { (entity, bytes) ⇒
|
||||
val charBuffer = entity.contentType.charset.nioCharset.decode(bytes.asByteBuffer)
|
||||
val array = new Array[Char](charBuffer.length())
|
||||
charBuffer.get(array)
|
||||
array
|
||||
if (entity.isKnownEmpty) Array.emptyCharArray
|
||||
else {
|
||||
val charBuffer = Unmarshaller.bestUnmarshallingCharsetFor(entity).nioCharset.decode(bytes.asByteBuffer)
|
||||
val array = new Array[Char](charBuffer.length())
|
||||
charBuffer.get(array)
|
||||
array
|
||||
}
|
||||
}
|
||||
|
||||
implicit def stringUnmarshaller: FromEntityUnmarshaller[String] =
|
||||
byteStringUnmarshaller mapWithInput { (entity, bytes) ⇒
|
||||
// FIXME: add `ByteString::decodeString(java.nio.Charset): String` overload!!!
|
||||
bytes.decodeString(entity.contentType.charset.nioCharset.name) // ouch!!!
|
||||
if (entity.isKnownEmpty) ""
|
||||
else bytes.decodeString(Unmarshaller.bestUnmarshallingCharsetFor(entity).nioCharset.name)
|
||||
}
|
||||
|
||||
implicit def defaultUrlEncodedFormDataUnmarshaller: FromEntityUnmarshaller[FormData] =
|
||||
urlEncodedFormDataUnmarshaller(MediaTypes.`application/x-www-form-urlencoded`)
|
||||
def urlEncodedFormDataUnmarshaller(ranges: ContentTypeRange*): FromEntityUnmarshaller[FormData] =
|
||||
stringUnmarshaller.forContentTypes(ranges: _*).mapWithInput { (entity, string) ⇒
|
||||
try {
|
||||
val nioCharset = entity.contentType.definedCharset.getOrElse(HttpCharsets.`UTF-8`).nioCharset
|
||||
val query = Uri.Query(string, nioCharset)
|
||||
FormData(query)
|
||||
} catch {
|
||||
case IllegalUriException(info) ⇒
|
||||
throw new IllegalArgumentException(info.formatPretty.replace("Query,", "form content,"))
|
||||
if (entity.isKnownEmpty) FormData.Empty
|
||||
else {
|
||||
try FormData(Uri.Query(string, Unmarshaller.bestUnmarshallingCharsetFor(entity).nioCharset))
|
||||
catch {
|
||||
case IllegalUriException(info) ⇒
|
||||
throw new IllegalArgumentException(info.formatPretty.replace("Query,", "form content,"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ object Unmarshaller
|
|||
/**
|
||||
* Creates an `Unmarshaller` from the given function.
|
||||
*/
|
||||
def apply[A, B](f: ExecutionContext ⇒ A ⇒ Future[B]): Unmarshaller[A, B] =
|
||||
def apply[A, B](f: ExecutionContext ⇒ A ⇒ Future[B]): Unmarshaller[A, B] =
|
||||
withMaterializer(ec => _ => f(ec))
|
||||
|
||||
def withMaterializer[A, B](f: ExecutionContext ⇒ Materializer => A ⇒ Future[B]): Unmarshaller[A, B] =
|
||||
|
|
@ -90,29 +90,43 @@ object Unmarshaller
|
|||
|
||||
implicit class EnhancedFromEntityUnmarshaller[A](val underlying: FromEntityUnmarshaller[A]) extends AnyVal {
|
||||
def mapWithCharset[B](f: (A, HttpCharset) ⇒ B): FromEntityUnmarshaller[B] =
|
||||
underlying.mapWithInput { (entity, data) ⇒ f(data, entity.contentType.charset) }
|
||||
underlying.mapWithInput { (entity, data) ⇒ f(data, Unmarshaller.bestUnmarshallingCharsetFor(entity)) }
|
||||
|
||||
/**
|
||||
* Modifies the underlying [[Unmarshaller]] to only accept content-types matching one of the given ranges.
|
||||
* If the underlying [[Unmarshaller]] already contains a content-type filter (also wrapped at some level),
|
||||
* this filter is *replaced* by this method, not stacked!
|
||||
* Modifies the underlying [[Unmarshaller]] to only accept Content-Types matching one of the given ranges.
|
||||
* Note that you can only restrict to a subset of the Content-Types accepted by the underlying unmarshaller,
|
||||
* i.e. the given ranges must be completely supported also by the underlying Unmarshaller!
|
||||
* If a violation of this rule is detected at runtime, i.e. if an entity is encountered whose Content-Type
|
||||
* is matched by one of the given ranges but rejected by the underlying unmarshaller
|
||||
* an IllegalStateException will be thrown!
|
||||
*/
|
||||
def forContentTypes(ranges: ContentTypeRange*): FromEntityUnmarshaller[A] =
|
||||
Unmarshaller.withMaterializer { implicit ec ⇒
|
||||
implicit mat ⇒
|
||||
entity ⇒
|
||||
if (entity.contentType == ContentTypes.NoContentType || ranges.exists(_ matches entity.contentType)) {
|
||||
underlying(entity).fast recoverWith retryWithPatchedContentType(underlying, entity)
|
||||
underlying(entity).fast.recover[A](barkAtUnsupportedContentTypeException(ranges, entity.contentType))
|
||||
} else FastFuture.failed(UnsupportedContentTypeException(ranges: _*))
|
||||
}
|
||||
|
||||
// TODO: move back into the [[EnhancedFromEntityUnmarshaller]] value class after the upgrade to Scala 2.11,
|
||||
// Scala 2.10 suffers from this bug: https://issues.scala-lang.org/browse/SI-8018
|
||||
private def barkAtUnsupportedContentTypeException(ranges: Seq[ContentTypeRange],
|
||||
newContentType: ContentType): PartialFunction[Throwable, Nothing] = {
|
||||
case UnsupportedContentTypeException(supported) ⇒ throw new IllegalStateException(
|
||||
s"Illegal use of `unmarshaller.forContentTypes($ranges)`: $newContentType is not supported by underlying marshaller!")
|
||||
}
|
||||
}
|
||||
|
||||
// must be moved out of the the [[EnhancedFromEntityUnmarshaller]] value class due to bug in scala 2.10:
|
||||
// https://issues.scala-lang.org/browse/SI-8018
|
||||
private def retryWithPatchedContentType[T](underlying: FromEntityUnmarshaller[T], entity: HttpEntity)(
|
||||
implicit ec: ExecutionContext, mat: Materializer): PartialFunction[Throwable, Future[T]] = {
|
||||
case UnsupportedContentTypeException(supported) ⇒ underlying(entity withContentType supported.head.specimen)
|
||||
}
|
||||
/**
|
||||
* Returns the best charset for unmarshalling the given entity to a character-based representation.
|
||||
* Falls back to UTF-8 if no better alternative can be determined.
|
||||
*/
|
||||
def bestUnmarshallingCharsetFor(entity: HttpEntity): HttpCharset =
|
||||
entity.contentType match {
|
||||
case x: ContentType.NonBinary ⇒ x.charset
|
||||
case _ ⇒ HttpCharsets.`UTF-8`
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that unmarshalling failed because the entity was unexpectedly empty.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue