From b1844f09d78e035d85302310e2ba55929cd5fc52 Mon Sep 17 00:00:00 2001 From: adamw Date: Sun, 16 Jul 2017 09:59:09 +0200 Subject: Streaming tests, comments --- .../sttp/HttpConnectionSttpHandler.scala | 3 +- .../scala/com/softwaremill/sttp/Response.scala | 12 +-- .../com/softwaremill/sttp/UriInterpolator.scala | 15 ++-- .../main/scala/com/softwaremill/sttp/package.scala | 91 +++++++++++++++------- 4 files changed, 82 insertions(+), 39 deletions(-) (limited to 'core/src') diff --git a/core/src/main/scala/com/softwaremill/sttp/HttpConnectionSttpHandler.scala b/core/src/main/scala/com/softwaremill/sttp/HttpConnectionSttpHandler.scala index dc6f8a2..50f6d76 100644 --- a/core/src/main/scala/com/softwaremill/sttp/HttpConnectionSttpHandler.scala +++ b/core/src/main/scala/com/softwaremill/sttp/HttpConnectionSttpHandler.scala @@ -120,7 +120,8 @@ object HttpConnectionSttpHandler extends SttpHandler[Id, Nothing] { r.parse(asString(enc)) case ResponseAsStream() => - // only possible when the user requests the response as a stream of Nothing. Oh well ... + // only possible when the user requests the response as a stream of + // Nothing. Oh well ... throw new IllegalStateException() } } diff --git a/core/src/main/scala/com/softwaremill/sttp/Response.scala b/core/src/main/scala/com/softwaremill/sttp/Response.scala index a4d60a5..44c39c3 100644 --- a/core/src/main/scala/com/softwaremill/sttp/Response.scala +++ b/core/src/main/scala/com/softwaremill/sttp/Response.scala @@ -47,9 +47,9 @@ case class Cookie(name: String, object Cookie { def apply(hc: HttpCookie, h: String): Cookie = { - // HttpCookie.parse has special handling for the expires attribute and turns it into max-age - // if the cookie contains an expires header; hand-parsing in such case to preserve the - // values from the cookie + // HttpCookie.parse has special handling for the expires attribute and + // turns it into max-age if the cookie contains an expires header; + // hand-parsing in such case to preserve the values from the cookie val lch = h.toLowerCase val (expires, maxAge) = if (lch.contains("expires=")) { val tokenizer = new StringTokenizer(h, ";") @@ -85,7 +85,8 @@ object Cookie { } /** - * Modified version of `HttpCookie.expiryDate2DeltaSeconds` to return a `ZonedDateTime`, not a second-delta. + * Modified version of `HttpCookie.expiryDate2DeltaSeconds` to return a + * `ZonedDateTime`, not a second-delta. */ private def expiryDate2ZonedDateTime( dateString: String): Option[ZonedDateTime] = { @@ -98,7 +99,8 @@ object Cookie { df.set2DigitYearStart(cal.getTime) try { cal.setTime(df.parse(dateString)) - if (!format.contains("yyyy")) { // 2-digit years following the standard set + if (!format.contains("yyyy")) { + // 2-digit years following the standard set // out it rfc 6265 var year = cal.get(Calendar.YEAR) year %= 100 diff --git a/core/src/main/scala/com/softwaremill/sttp/UriInterpolator.scala b/core/src/main/scala/com/softwaremill/sttp/UriInterpolator.scala index 1216a1e..a8099a4 100644 --- a/core/src/main/scala/com/softwaremill/sttp/UriInterpolator.scala +++ b/core/src/main/scala/com/softwaremill/sttp/UriInterpolator.scala @@ -44,9 +44,10 @@ object UriInterpolator { case Array(x) => if (!x.matches("[a-zA-Z0-9+\\.\\-]*")) { - // anything else than the allowed characters in scheme suggest that there is no scheme - // assuming whatever we parsed so far is part of authority, and parsing the rest - // see https://stackoverflow.com/questions/3641722/valid-characters-for-uri-schemes + // anything else than the allowed characters in scheme suggest that + // there is no scheme assuming whatever we parsed so far is part of + // authority, and parsing the rest; see + // https://stackoverflow.com/questions/3641722/valid-characters-for-uri-schemes Authority(Scheme(""), v).parseS(x) } else append(x) } @@ -54,8 +55,8 @@ object UriInterpolator { override def parseE(e: Any): UriBuilder = { def encodeIfNotInitialEndpoint(s: String) = { - // special case: when this is the first expression, contains a complete schema with :// and nothing is yet parsed - // not escaping the contents + // special case: when this is the first expression, contains a complete + // schema with :// and nothing is yet parsed not escaping the contents if (v.isEmpty && s.contains("://")) s else encode(s) } @@ -314,8 +315,8 @@ object UriInterpolator { private def encode(s: Any): String = { // space is encoded as a +, which is only valid in the query; - // in other contexts, it must be percent-encoded - // see https://stackoverflow.com/questions/2678551/when-to-encode-space-to-plus-or-20 + // in other contexts, it must be percent-encoded; see + // https://stackoverflow.com/questions/2678551/when-to-encode-space-to-plus-or-20 URLEncoder.encode(String.valueOf(s), "UTF-8").replaceAll("\\+", "%20") } diff --git a/core/src/main/scala/com/softwaremill/sttp/package.scala b/core/src/main/scala/com/softwaremill/sttp/package.scala index baf4061..763b6c3 100644 --- a/core/src/main/scala/com/softwaremill/sttp/package.scala +++ b/core/src/main/scala/com/softwaremill/sttp/package.scala @@ -26,6 +26,10 @@ package object sttp { ResponseAsString(encoding) def responseAsByteArray: ResponseAs[Array[Byte], Nothing] = ResponseAsByteArray + + /** + * Uses `utf-8` encoding. + */ def responseAsParams: ResponseAs[Seq[(String, String)], Nothing] = responseAsParams(Utf8) def responseAsParams( @@ -35,8 +39,9 @@ package object sttp { def responseAsStream[S]: ResponseAs[S, S] = ResponseAsStream[S, S]() /** - * Use the factory methods `multiPart` to conveniently create instances of this class. A part can be then - * further customised using `fileName`, `contentType` and `header` methods. + * Use the factory methods `multiPart` to conveniently create instances of + * this class. A part can be then further customised using `fileName`, + * `contentType` and `header` methods. */ case class MultiPart(name: String, data: BasicRequestBody, @@ -50,7 +55,8 @@ package object sttp { } /** - * Content type will be set to `text/plain` with `utf-8` encoding, can be overridden later using the `contentType` method. + * Content type will be set to `text/plain` with `utf-8` encoding, can be + * overridden later using the `contentType` method. */ def multiPart(name: String, data: String): MultiPart = MultiPart(name, @@ -59,7 +65,8 @@ package object sttp { Some(contentTypeWithEncoding(TextPlainContentType, Utf8))) /** - * Content type will be set to `text/plain` with `utf-8` encoding, can be overridden later using the `contentType` method. + * Content type will be set to `text/plain` with `utf-8` encoding, can be + * overridden later using the `contentType` method. */ def multiPart(name: String, data: String, encoding: String): MultiPart = MultiPart(name, @@ -68,7 +75,8 @@ package object sttp { Some(contentTypeWithEncoding(TextPlainContentType, Utf8))) /** - * Content type will be set to `application/octet-stream`, can be overridden later using the `contentType` method. + * Content type will be set to `application/octet-stream`, can be overridden + * later using the `contentType` method. */ def multiPart(name: String, data: Array[Byte]): MultiPart = MultiPart(name, @@ -76,7 +84,8 @@ package object sttp { contentType = Some(ApplicationOctetStreamContentType)) /** - * Content type will be set to `application/octet-stream`, can be overridden later using the `contentType` method. + * Content type will be set to `application/octet-stream`, can be overridden + * later using the `contentType` method. */ def multiPart(name: String, data: ByteBuffer): MultiPart = MultiPart(name, @@ -84,7 +93,8 @@ package object sttp { contentType = Some(ApplicationOctetStreamContentType)) /** - * Content type will be set to `application/octet-stream`, can be overridden later using the `contentType` method. + * Content type will be set to `application/octet-stream`, can be overridden + * later using the `contentType` method. */ def multiPart(name: String, data: InputStream): MultiPart = MultiPart(name, @@ -92,13 +102,15 @@ package object sttp { contentType = Some(ApplicationOctetStreamContentType)) /** - * Content type will be set to `application/octet-stream`, can be overridden later using the `contentType` method. + * Content type will be set to `application/octet-stream`, can be overridden + * later using the `contentType` method. */ def multiPart(name: String, data: File): MultiPart = multiPart(name, data.toPath) /** - * Content type will be set to `application/octet-stream`, can be overridden later using the `contentType` method. + * Content type will be set to `application/octet-stream`, can be overridden + * later using the `contentType` method. */ def multiPart(name: String, data: Path): MultiPart = MultiPart(name, @@ -106,6 +118,17 @@ package object sttp { fileName = Some(data.getFileName.toString), contentType = Some(ApplicationOctetStreamContentType)) + /** + * @tparam U Specifies if the method & uri are specified. By default can be + * either: + * * `Empty`, which is a type constructor which always resolves to + * `None`. This type of request is aliased to `PartialRequest`: + * there's no method and uri specified, and the request cannot be + * sent. + * * `Id`, which is an identity type constructor. This type of + * request is aliased to `Request`: the method and uri are + * specified, and the request can be sent. + */ case class RequestTemplate[U[_]]( method: U[Method], uri: U[URI], @@ -159,12 +182,14 @@ package object sttp { /** * Uses the `utf-8` encoding. - * If content type is not yet specified, will be set to `text/plain` with `utf-8` encoding. + * If content type is not yet specified, will be set to `text/plain` + * with `utf-8` encoding. */ def body(b: String): RequestTemplate[U] = body(b, Utf8) /** - * If content type is not yet specified, will be set to `text/plain` with the given encoding. + * If content type is not yet specified, will be set to `text/plain` + * with the given encoding. */ def body(b: String, encoding: String): RequestTemplate[U] = setContentTypeIfMissing( @@ -172,33 +197,38 @@ package object sttp { .copy(body = StringBody(b, encoding)) /** - * If content type is not yet specified, will be set to `application/octet-stream`. + * If content type is not yet specified, will be set to + * `application/octet-stream`. */ def body(b: Array[Byte]): RequestTemplate[U] = setContentTypeIfMissing(ApplicationOctetStreamContentType).copy( body = ByteArrayBody(b)) /** - * If content type is not yet specified, will be set to `application/octet-stream`. + * If content type is not yet specified, will be set to + * `application/octet-stream`. */ def body(b: ByteBuffer): RequestTemplate[U] = setContentTypeIfMissing(ApplicationOctetStreamContentType).copy( body = ByteBufferBody(b)) /** - * If content type is not yet specified, will be set to `application/octet-stream`. + * If content type is not yet specified, will be set to + * `application/octet-stream`. */ def body(b: InputStream): RequestTemplate[U] = setContentTypeIfMissing(ApplicationOctetStreamContentType).copy( body = InputStreamBody(b)) /** - * If content type is not yet specified, will be set to `application/octet-stream`. + * If content type is not yet specified, will be set to + * `application/octet-stream`. */ def body(b: File): RequestTemplate[U] = body(b.toPath) /** - * If content type is not yet specified, will be set to `application/octet-stream`. + * If content type is not yet specified, will be set to + * `application/octet-stream`. */ def body(b: Path): RequestTemplate[U] = setContentTypeIfMissing(ApplicationOctetStreamContentType).copy( @@ -206,34 +236,39 @@ package object sttp { /** * Encodes the given parameters as form data using `utf-8`. - * If content type is not yet specified, will be set to `application/x-www-form-urlencoded`. + * If content type is not yet specified, will be set to + * `application/x-www-form-urlencoded`. */ def body(fs: Map[String, String]): RequestTemplate[U] = formDataBody(fs.toList, Utf8) /** * Encodes the given parameters as form data. - * If content type is not yet specified, will be set to `application/x-www-form-urlencoded`. + * If content type is not yet specified, will be set to + * `application/x-www-form-urlencoded`. */ def body(fs: Map[String, String], encoding: String): RequestTemplate[U] = formDataBody(fs.toList, encoding) /** * Encodes the given parameters as form data using `utf-8`. - * If content type is not yet specified, will be set to `application/x-www-form-urlencoded`. + * If content type is not yet specified, will be set to + * `application/x-www-form-urlencoded`. */ def body(fs: (String, String)*): RequestTemplate[U] = formDataBody(fs.toList, Utf8) /** * Encodes the given parameters as form data. - * If content type is not yet specified, will be set to `application/x-www-form-urlencoded`. + * If content type is not yet specified, will be set to + * `application/x-www-form-urlencoded`. */ def body(fs: Seq[(String, String)], encoding: String): RequestTemplate[U] = formDataBody(fs, encoding) /** - * If content type is not yet specified, will be set to `application/octet-stream`. + * If content type is not yet specified, will be set to + * `application/octet-stream`. */ def body[T: BodySerializer](b: T): RequestTemplate[U] = setContentTypeIfMissing(ApplicationOctetStreamContentType).copy( @@ -242,10 +277,13 @@ package object sttp { //def multipartData(parts: MultiPart*): RequestTemplate[U] = ??? /** - * @param responseAs What's the target type to which the response should be read. Needs to be specified upfront - * so that the response is always consumed and hence there are no requirements on client code - * to consume it. An exception to this are streaming responses, which need to fully consumed - * by the client if such a response type is requested. + * @param responseAs What's the target type to which the response body + * should be read. Needs to be specified upfront + * so that the response is always consumed and hence + * there are no requirements on client code to consume + * it. An exception to this are streaming responses, + * which need to fully consumed by the client if such + * a response type is requested. */ def send[R[_], S, T](responseAs: ResponseAs[T, S])( implicit handler: SttpHandler[R, S], @@ -293,7 +331,8 @@ package object sttp { type Request = RequestTemplate[Id] @implicitNotFound( - "This is a partial request, the method & url are not specified. Use .get(...), .post(...) etc. to obtain a non-partial request.") + "This is a partial request, the method & url are not specified. Use " + + ".get(...), .post(...) etc. to obtain a non-partial request.") private type IsRequest[U[_]] = RequestTemplate[U] =:= Request val sttp: RequestTemplate[Empty] = RequestTemplate.empty -- cgit v1.2.3