aboutsummaryrefslogtreecommitdiff
path: root/docs/responses
diff options
context:
space:
mode:
authoradamw <adam@warski.org>2017-10-17 17:28:50 +0200
committeradamw <adam@warski.org>2017-10-17 17:28:50 +0200
commit6e109a964383bfe5e2be04f65fa7cc1356a97cbe (patch)
tree7764ff94d72e0ffbf1e593fb8c5886562dc57f33 /docs/responses
parent06bd5c95d04dd57e1b6c2572b94336b8fdb68bfa (diff)
downloadsttp-6e109a964383bfe5e2be04f65fa7cc1356a97cbe.tar.gz
sttp-6e109a964383bfe5e2be04f65fa7cc1356a97cbe.tar.bz2
sttp-6e109a964383bfe5e2be04f65fa7cc1356a97cbe.zip
More docs
Diffstat (limited to 'docs/responses')
-rw-r--r--docs/responses/basics.rst50
-rw-r--r--docs/responses/body.rst86
2 files changed, 136 insertions, 0 deletions
diff --git a/docs/responses/basics.rst b/docs/responses/basics.rst
new file mode 100644
index 0000000..6157647
--- /dev/null
+++ b/docs/responses/basics.rst
@@ -0,0 +1,50 @@
+Responses
+=========
+
+Responses are represented as instances of the case class ``Response[T]``, where ``T`` is the type of the response body. When sending a request, the response will be returned in a wrapper. For example, for asynchronous backends, we will get a ``Future[Response[T]]``, while for the default synchronous backend, the wrapper backend will be the no-op wrapper, ``Id``, which is the same as no wrapper at all.
+
+If sending the request fails, either due to client or connection errors, an exception will be thrown (synchronous backends), or an error will be represented in the wrapper (e.g. a failed future).
+
+.. note::
+
+ If the request completes, but results in a non-2xx return code, the request is still considered successful, that is, a ``Response[T]`` will be returned. See the next subsection for details regarding body handling.
+
+Response code
+-------------
+
+The response code is available through the ``.code`` property. There are also methods such as ``.isSuccess`` or ``.isServerError`` for checking specific response code ranges.
+
+Response headers
+----------------
+
+Response headers are available through the ``.headers`` property, which gives all headers as a sequence (not as a map, as there can be multiple headers with the same name).
+
+Individual headers can be obtained using the methods::
+
+ def header(h: String): Option[String]
+ def headers(h: String): Seq[String]
+
+There are also helper methods available to read some commonly accessed headers::
+
+ def contentType: Option[String]
+ def contentLength: Option[Long]
+
+Finally, it's possible to parse the response cookies into a sequence of the ``Cookie`` case class::
+
+ def cookies: Seq[Cookie]
+
+If the cookies from a response should be set without changes on the request, this can be done directly; see the :ref:`cookies <cookies>` section in the request definition documentation.
+
+Obtaining the response body
+---------------------------
+
+The response body can be obtained through the ``.body`` property, which has type ``Either[String, T]``. ``T`` is the body deserialized as specified in the request - see the next section on :ref:`response body specifications <responsebodyspec>`.
+
+The response body is an either as the response body can only be deserialized if the server responded with code 200. Otherwise, the response body is most probably an error message.
+
+Hence, the ``response.body`` will be a:
+
+* ``Left(errorMessage)`` if the request is successful, but response code is not 2xx.
+* ``Right(deserializedBody``) if the request is successful and the response code is 2xx.
+
+You can also forcibly get the deserialized body, risking an excepiton being thrown, using the ``response.unsafeBody`` method.
diff --git a/docs/responses/body.rst b/docs/responses/body.rst
new file mode 100644
index 0000000..da3009c
--- /dev/null
+++ b/docs/responses/body.rst
@@ -0,0 +1,86 @@
+.. _responsebodyspec:
+
+Response body specification
+===========================
+
+By default, the received response body will be read as a ``String``, using the ``UTF-8`` encoding. This is of course configurable: response bodies can be ignored, deserialized into custom types, recevied as a stream or saved to a file.
+
+How the response body will be read is part of the request definition, as already when sending the request, the backend needs to know what to do with the response. The type to which the response body should be deserialized is the second type parameter of ``RequestT``, and stored in the request definition as ``request.response``, which has type ``ResponseAs[T, S]``.
+
+Basic response specifications
+-----------------------------
+
+To conveniently specify how to deserialize the response body, a number of ``as[Type]`` methods are available. They can be used to provide a value for the requet's ``response`` modifier::
+
+ sttp.response(asByteArray)
+
+When the above request is completed and sent, it will result in a ``Response[Array[Byte]]``. Other possible response specifications are::
+
+ def ignore: ResponseAs[Unit, Nothing]
+ def asString: ResponseAs[String, Nothing]
+ def asString(encoding: String): ResponseAs[String, Nothing]
+ def asByteArray: ResponseAs[Array[Byte], Nothing]
+ def asParams: ResponseAs[Seq[(String, String)], Nothing]
+ def asParams(encoding: String): ResponseAs[Seq[(String, String)], Nothing] =
+ def asFile(file: File, overwrite: Boolean = false): ResponseAs[File, Nothing]
+ def asPath(path: Path, overwrite: Boolean = false): ResponseAs[Path, Nothing]
+
+Hence, to discard the response body, simply specify::
+
+ sttp.response(ignore)
+
+And to save the response to a file::
+
+ sttp.respone(asFile(someFile))
+
+.. note::
+
+ As the handling of response is specified upfront, there's no need to "consume" the response body. It can be safely discarded if not needed.
+
+Custom body deserializers
+-------------------------
+
+It's possible to define custom body deserializers by taking any of the built-in response specifications and mapping over them. Each ``ResponseAs`` instance has a ``map`` method, which can be used to transform it to a specification for another type. Each such value is immutable and can be used multiple times.
+
+As an example, to read the response body as an int, the following response specification can be defined (warning: this ignores the possibility of exceptions!)::
+
+ val asInt: ResponseAs[Int, Nothing] = asString.map(_.toInt)
+
+ sttp
+ .response(asInt)
+ ...
+
+To integrate with a third-party JSON library::
+
+ def parseJson(json: String): Either[JsonError, JsonAST] = ...
+ val asJson: ResponseAs[Either[JsonError, JsonAST], Nothing] = asString.map(parseJson)
+
+For some mapped response specifications available out-of-the-box, see :ref:`json support <json>`.
+
+Streaming
+---------
+
+If the backend used supports streaming (see :ref:`backends summary <backends_summary>`), it's possible to receive responses as a stream. This can be specified using the following method::
+
+ def asStream[S]: ResponseAs[S, S] = ResponseAsStream[S, S]()
+
+For example, when using the :ref:`akka-http backend <akkahttp>`::
+
+ import com.softwaremill.sttp._
+ import com.softwaremill.sttp.akkahttp._
+
+ import akka.stream.scaladsl.Source
+ import akka.util.ByteString
+
+ implicit val sttpBackend = AkkaHttpBackend()
+
+ val response: Future[Response[Source[ByteString, Any]]] =
+ sttp
+ .post(uri"...")
+ .response(asStream[Source[ByteString, Any]])
+ .send()
+
+.. note::
+
+ Unlike with non-streaming response handlers, each streaming response should be entirely consumed by client code.
+