diff options
Diffstat (limited to 'docs/requests')
-rw-r--r-- | docs/requests/authentication.rst | 13 | ||||
-rw-r--r-- | docs/requests/basics.rst | 52 | ||||
-rw-r--r-- | docs/requests/body.rst | 81 | ||||
-rw-r--r-- | docs/requests/cookies.rst | 28 | ||||
-rw-r--r-- | docs/requests/defaults.rst | 11 | ||||
-rw-r--r-- | docs/requests/headers.rst | 30 | ||||
-rw-r--r-- | docs/requests/multipart.rst | 42 | ||||
-rw-r--r-- | docs/requests/streaming.rst | 24 | ||||
-rw-r--r-- | docs/requests/uri.rst | 71 |
9 files changed, 304 insertions, 48 deletions
diff --git a/docs/requests/authentication.rst b/docs/requests/authentication.rst new file mode 100644 index 0000000..f97f74a --- /dev/null +++ b/docs/requests/authentication.rst @@ -0,0 +1,13 @@ +Authentication +============== + +sttp supports basic and bearer-token based authentication. In both cases, an ``Authorization`` header is added with the appropriate credentials. + +Basic authentication, using which the username and password are encoded using Base64, can be added as follows:: + + sttp.auth.basic(username, password) + +A bearer token can be added using:: + + sttp.auth.bearer(token) + diff --git a/docs/requests/basics.rst b/docs/requests/basics.rst index 6a6202c..fc15f36 100644 --- a/docs/requests/basics.rst +++ b/docs/requests/basics.rst @@ -1,43 +1,47 @@ -Defining requests: basics +.. _request_basics: + +Request definition basics ========================= -To get easy access to the request definition API, add the following import:: +As mentioned in the :ref:`quickstart <quickstart>`, the following import will be needed:: import com.softwaremill.sttp._ -This brings into scope ``sttp``, the starting request (it's an empty request -with the ``Accept-Encoding: gzip, defalte`` header added). This request can -be customised, each time yielding a new, immutable request description -(unless a mutable body is set on the request, such as a byte array). +This brings into scope ``sttp``, the starting request. This request can be customised, each time yielding a new, immutable request description (unless a mutable body is set on the request, such as a byte array). As the request description is immutable, it can be freely stored in values, shared across threads, and customized multiple times. -For example, we can set a cookie, string-body and specify that this should -be a ``POST`` request to a given URI:: +For example, we can set a cookie, string-body and specify that this should be a ``POST`` request to a given URI:: val request = sttp .cookie("login", "me") .body("This is a test") .post(uri"http://endpoint.com/secret") + +The request parameters (headers, cookies, body etc.) can be specified **in any order**. It doesn't matter if the request method, the body, the headers or connection options are specified in this sequence or another. This way you can build arbitrary request templates, capturing all that's common among your requests, and customizing as needed. Remember, that each time a modifier is applied to a request, you get a new immutable object. + +There's a lot of ways in which you can customize a request, which are covered in this guide. Another option is to just explore the API: most of the methods are self-explanatory and carry scaladocs if needed. + +Using the modifiers, each time we get a new request description, but it's just description: a data object; nothing is sent over the network until the ``send()`` method is invoked. + +Sending a request +----------------- -The request parameters (headers, cookies, body etc.) can be specified in any -order. There's a lot of ways in which you can customize a request: just -explore the API. +A request description can be created without knowing how it will be sent. But to send a request, a backend is needed. A default, synchronous backend based on Java's ``HttpURLConnection`` is provided out-of-the box. -You can create a request description without knowing how it will be sent. -But to send a request, you will need a backend. A default, synchronous backend -based on Java's ``HttpURLConnection`` is provided out-of-the box. An implicit -value of type ``SttpBackend`` needs to be in scope to invoke the ``send()`` on the -request:: +To invoke the ``send()`` method on a request description, an implicit value of type ``SttpBackend`` needs to be in scope:: implicit val backend = HttpURLConnectionBackend() val response: Response[String] = request.send() -By default the response body is read into a utf-8 string. How the response body -is handled is also part of the request description. The body can be ignore -(``.response(ignore)``), read into a sequence of parameters -(``.response(asParams)``), mapped (``.mapResponse``) and more; some backends also -support request & response streaming. +The default backend doesn't wrap the response into any container, but other asynchronous backends might do so. See the section on :ref:`backends <backends_summary>` for more details. + +Starting requests +----------------- + +sttp provides two starting requests: + +* ``sttp``, which is an empty request with the ``Accept-Encoding: gzip, deflate`` header added. That's the one that is most commonly used. +* ``empty``, a completely empty request, with no headers at all. + +Both of these requests will by default read the response body into a UTF-8 ``String``. How the response body is handled is also part of the request description. See the section on :ref:`response body specifications <responsebodyspec>` for more details on how to customize that. -The default backend doesn't wrap the response into any container, but other -asynchronous backends might do so. The type parameter in the ``Response[_]`` -type specifies the type of the body. diff --git a/docs/requests/body.rst b/docs/requests/body.rst new file mode 100644 index 0000000..bae7526 --- /dev/null +++ b/docs/requests/body.rst @@ -0,0 +1,81 @@ +Request bodies +============== + +Text data +--------- + +In its simplest form, the request's body can be set as a ``String``. By default, this method will: + +* use the UTF-8 encoding to convert the string to a byte array +* if not specified, set ``Content-Type: text/plain`` +* if not specified, set ``Content-Length`` to the number of bytes in the array + +A ``String`` body can be set on a request as follows:: + + sttp.body("Hello, world!") + +It is also possible to use a different character encoding:: + + def body(b: String) + def body(b: String, encoding: String) + +Binary data +----------- + +To set a binary-data body, the following methods are available:: + + def body(b: Array[Byte]) + def body(b: ByteBuffer) + def body(b: InputStream) + +If not specified, these methods will set the conten type to ``application/octet-stream``. When using a byte array, additionally the content length will be set to the length of the array (unless specified explicitly). + +.. note:: + + While the object defining a request is immutable, setting a mutable request body will make the whole request definition mutable as well. With ``InputStream``, the request can be sent only once, as input streams can be consumed once. + +Uploading files +--------------- + +To upload a file, simply set the request body as a ``File``, ``Path``:: + + def body(b: File) + def body(b: Path) + +As with binary body methods, the content type will default to ``application/octet-stream``, and the content length will be set to the lenght of the file (unless specified explicitly). + +See also :ref:`multi-part <multipart>` and :ref:`streaming <streaming>` support. + +Form data +--------- + +If you set the body as a ``Map[String, String]`` or ``Seq[(String, String)]``, it will be encoded as form-data (as if a web form with the given values was submitted). The content type will default to ``application/x-www-form-urlencoded``; content length will also be set if not specified. + +By default, the ``UTF-8`` encoding is used, but can be also specified explicitly:: + + def body(fs: Map[String, String]) + def body(fs: Map[String, String], encoding: String) + def body(fs: (String, String)*) + def body(fs: Seq[(String, String)], encoding: String) + +Custom body serializers +----------------------- + +It is also possible to set custom types as request bodies, as long as there's an implicit ``BodySerializer[B]`` value in scope, which is just an alias for a function:: + + type BodySerializer[B] = B => BasicRequestBody + +A ``BasicRequestBody`` is a wrapper for a ``String``/byte array/input stream body. For example, here's how to write custom serializer for a case class, with serializer-specific default content type:: + + case class Person(name: String, surname: String, age: Int) + + // for this example, assuming names/surnames can't contain commas + implicit val personSerializer: BodySerializer[Person] = { p: Person => + val serialized = s"${p.name},${p.surname},${p.age}" + StringBody(serialized, "UTF-8", Some("application/csv")) + } + + sttp.body(Person("mary", "smith", 67)) + +See the implementations of the ``BasicRequestBody`` trait for more options. + diff --git a/docs/requests/cookies.rst b/docs/requests/cookies.rst new file mode 100644 index 0000000..0645316 --- /dev/null +++ b/docs/requests/cookies.rst @@ -0,0 +1,28 @@ +.. _cookies: + +Cookies +======= + +Cookies sent in requests are key-value pairs contained in the ``Cookie`` header. They can be set on a request in a couple of ways. The first is using the ``.cookie(name: String, value: String)`` method. This will yield a new request definition which, when sent, will contain the given cookie. + +Cookies can also be set using the following methods:: + + def cookie(nv: (String, String)) + def cookie(n: String, v: String) + def cookies(nvs: (String, String)*) + +Cookies from responses +---------------------- + +It is often necessary to copy cookies from a response, e.g. after a login request is sent, and a successful response with the authentication cookie received. Having an object ``response: Response[_]``, cookies on a request can be copied:: + + // Method signature + def cookies(r: Response[_]) + + // Usage + sttp.cookies(response) + +Or, it's also possible to store only the ``com.softwaremill.sttp.Cookie`` objects (a sequence of which can be obtained from a response), and set the on the request:: + + def cookies(cs: Seq[Cookie]) + diff --git a/docs/requests/defaults.rst b/docs/requests/defaults.rst deleted file mode 100644 index f393c04..0000000 --- a/docs/requests/defaults.rst +++ /dev/null @@ -1,11 +0,0 @@ -Defaults -======== - -The encoding for ``String``s defaults to ``utf-8``. - -Unless explicitly specified, the ``Content-Type`` defaults to: - -* ``text/plain`` for text -* ``application/x-www-form-urlencoded`` for form data -* ``multipart/form-data`` for multipart form data -* ``application/octet-stream`` for everything else (binary) diff --git a/docs/requests/headers.rst b/docs/requests/headers.rst new file mode 100644 index 0000000..1135377 --- /dev/null +++ b/docs/requests/headers.rst @@ -0,0 +1,30 @@ +Headers +======= + +Arbitrary headers can be set on the request using the ``.header`` method:: + + sttp.header("User-Agent", "myapp") + +As with any other request definition modifier, this method will yield a new request, which has the given header set. The headers can be set at any point when defining the request, arbitrarily interleaved with other modifiers. + +While most headers should be set only once on a request, HTTP allows setting a header multiple times. That's why the ``header`` method has an additional optional boolean parameter, ``replaceExisting``, which defaults to ``true``. This way, if the same header is specified twice, only the last value will be included in the request. If previous values should be preserved, set this parameter to ``false``. + +There are also variants of this method accepting a number of headers:: + + def header(k: String, v: String, replaceExisting: Boolean = false) + def headers(hs: Map[String, String]) + def headers(hs: (String, String)*) + +Both of the ``headers`` append the given headers to the ones currently in the request, without removing duplicates. + +Common headers +-------------- + +For some common headers, dedicated methods are provided:: + + def contentType(ct: String) + def contentType(ct: String, encoding: String) + def contentLength(l: Long) + def acceptEncoding(encoding: String) + +See also [cookies], [authentication]. diff --git a/docs/requests/multipart.rst b/docs/requests/multipart.rst new file mode 100644 index 0000000..5a1d11d --- /dev/null +++ b/docs/requests/multipart.rst @@ -0,0 +1,42 @@ +.. _multipart: + +Multipart requests +================== + +To set a multipart body on a request, the ``multipartBody`` method should be used (instead of ``body``). Each body part is represented as an instance of ``Multipart``, which can be conveniently constructed using ``multipart`` methods. + +A single part of a multipart request can be of type: + +* ``String`` +* ``Array[Byte]`` +* ``ByteBuffer`` +* ``InputStream`` +* ``File`` +* ``Path`` + +The content type of each part is by default the same as when setting simple bodies: ``text/plain`` for parts of type ``String``, ``application/x-www-form-urlencoded`` for parts of key-value pairs (form data) and ``application/octet-stream`` otherwise (for binary data). + +The parts can be specified using either a ``Seq[Multipart]`` or by using multiple arguments:: + + def multipartBody(ps: Seq[Multipart]) + def multipartBody(p1: Multipart, ps: Multipart*) + +For example:: + + sttp.multipartBody( + multipart("text_part", "data1"), + multipart("file_part", someFile), // someFile: File + multipart("form_part", Map("x" -> "10", "y" -> "yes")) + ) + +Customising part meta-data +-------------------------- + +For each part, an optional filename can be specified, as well as a custom content type and additional headers. The following methods are available on ``Multipart``:: + + case class Multipart { + def fileName(v: String): Multipart + def contentType(v: String): Multipart + def header(k: String, v: String): Multipart + } + diff --git a/docs/requests/streaming.rst b/docs/requests/streaming.rst new file mode 100644 index 0000000..600630c --- /dev/null +++ b/docs/requests/streaming.rst @@ -0,0 +1,24 @@ +.. _streaming: + +Streaming +========= + +Some backends (see :ref:`backends summary <backends_summary>`) support streaming bodies. If that's the case, you can set a stream of the supported type as a request body using the ``streamBody`` method. + +For example, using the :ref:`akka-http backend <akkahttp>`, a request with a stream body can be defined as follows:: + + import com.softwaremill.sttp._ + import com.softwaremill.sttp.akkahttp._ + + import akka.stream.scaladsl.Source + import akka.util.ByteString + + val source: Source[ByteString, Any] = ... + + sttp + .streamBody(source) + .post(uri"...") + +.. note:: + + A request with the body set as a stream can only be sent using a backend supporting exactly the given type of streams. diff --git a/docs/requests/uri.rst b/docs/requests/uri.rst index bcb7c88..17e0a2c 100644 --- a/docs/requests/uri.rst +++ b/docs/requests/uri.rst @@ -1,7 +1,16 @@ -Defining requests: URI Interpolator -=================================== +URIs +==== -Using the URI interpolator it's possible to conveniently create ``Uri` instances, which can then be used to specify request endpoints, for example:: +A request can only be sent if the request method & URI are defined. To represent URIs, sttp comes with a ``Uri`` case class, which captures all of the parts of an address. + +To specify the request method and URI, use one of the methods on the request description corresponding to the name of the method: ``.post``, ``.get``, ``.put`` etc. All of them accept a single parameter, the URI to which the request should be send, and yield a request description which, when sent, will use the given address (these methods only modify the request description; they don't send the requests). + +The ``Uri`` class is immutable, and can be constructed by hand, but in many cases the URI interpolator will be useful. + +URI interpolator +---------------- + +Using the URI interpolator it's possible to conveniently create ``Uri`` instances, for example:: import com.softwaremill.sttp._ @@ -10,18 +19,54 @@ Using the URI interpolator it's possible to conveniently create ``Uri` instances val endpoint: Uri = uri"http://example.com/$user/skills?filter=$filter" -Any values embedded in the URI will be URL-encoded, taking into account the -context (e.g., the whitespace in ``user`` will be %-encoded as ``%20D``, while the -whitespace in ``filter`` will be query-encoded as ``+``). + assert(endpoint.toString == "http://example.com/Mary%20Smith/skills?filter=programming+languages") + +Note the ``uri`` prefix before the string and the standard Scala string-embedding syntax (``$user``, ``$filter``). + +Any values embedded in the URI will be URL-encoded, taking into account the context (e.g., the whitespace in ``user`` will be %-encoded as ``%20D``, while the whitespace in ``filter`` will be query-encoded as ``+``). + +All components of the URI can be embedded from values: scheme, username/password, host, port, path, query and fragment. + +Optional values +--------------- + +The URI interpolator supports optional values for hosts (subdomains), query parameters and the fragment. If the value is ``None``, the appropriate URI component will be removed. For example:: + + val v1 = None + val v2 = Some("v2") + + val u1 = uri"http://example.com?p1=$v1&p2=v2" + assert(u1.toString == "http://example.com?p2=v2") + + val u2 = uri"http://$v1.$v2.example.com" + assert(u2.toString == "http://v2.example.com") + + val u3 = uri"http://example.com#$v1" + assert(u3.toString == "http://example.com") + +Maps and sequences +------------------ + +Maps, sequences of tuples and sequences of values can be embedded in the query part. They will be expanded into query parameters. Maps and sequences of tuples can also contain optional values, for which mappings will be removed if ``None``. + +For example:: + + val ps = Map("p1" -> "v1", "p2" -> "v2") + val u4 = uri"http://example.com?$ps&p3=p4" + assert(u4.toString == "http://example.com?p1=v1&p2=v2&p3=p4") + +Sequences in the host part will be expanded to a subdomain sequence. + +Special cases +------------- + +If a string containing the protocol is embedded *as the very beginning*, it will not be escaped, allowing to embed entire addresses as prefixes, e.g.: ``uri"$endpoint/login"``, where ``val endpoint = "http://example.com/api"``. + +This is useful when a base URI is stored in a value, and can then be used as a base for constructing URIs. -The possibilities of the interpolator don't end here. Other supported features: +All features combined +--------------------- -* parameters can have optional values: if the value of a parameter is ``None``, it will be removed -* maps, sequences of tuples and sequences of values can be embedded in the query part. They will be expanded into query parameters. Maps and sequences of tuples can also contain optional values, for which mappings will be removed if ``None``. -* optional values in the host part will be expanded to a subdomain if ``Some``, removed if ``None`` -* sequences in the host part will be expanded to a subdomain sequence -* if a string containing the protocol is embedded *as the very beginning*, it will not be escaped, allowing to embed entire addresses as prefixes, e.g.: ``uri"$endpoint/login"``, where ``val endpoint = "http://example.com/api"``. - A fully-featured example:: import com.softwaremill.sttp._ |