# Body encoding and decoding

# Encoding

Let’s consider an example. We develop an application which calculates `factorial` of a number:

    library(RestRserve)
    backend = BackendRserve$new()
    application = Application$new()
    application$add_get(path = "/factorial", function(.req, .res) {
      x = .req$get_param_query("x")
      x = as.integer(x)
      .res$set_body(factorial(x))
    })

Here is how request will be processed:

    request = Request$new(
      path = "/factorial",
      method = "GET",
      parameters_query = list(x = 10)
    )
    response = application$process_request(request)
    response
    #> <RestRserve Response>
    #>   status code: 200 OK
    #>   content-type: text/plain
    #>   <Headers>
    #>     Server: RestRserve/1.2.4; Rserve/1.8.13

Let’s take a closer look to the `response` object and its `body` property:

    str(response$body)
    #>  chr "3628800"

As we can see it is a numeric value. HTTP response body however can’t be an arbitrary R object. It should be something that external systems can understand - either `character` vector or `raw` vector. Fortunately `application` helps to avoid writing boilerplate code to encode the body. Based on the `content_type` property it can find `encode` function which will be used to transform `body` into a http body.

    response$content_type
    #> [1] "text/plain"

    response$encode
    #> NULL

Two immediate questions can arise:

1. Why `content_type` is equal to `text/plain`?
   - This is because we can specify default `content_type` in `Application` constructor. It is `text/plain` by default, which means all the responses by default will have `text/plain` content type.
2. How does application know how to encode `text/plain`? Can it encode any arbitrary content type?
   - Application by default is initialized with pre-defined ?EncodeDecodeMiddleware middleware. The logic on how to encode and decode request and response body is controlled by its `ContentHandlers` property. Out of the box it supports two content types - `text/plain` and `application/json`.

For instance `app1` and `app2` are equal:

    encode_decode_middleware = EncodeDecodeMiddleware$new()
    app1  = Application$new(middleware = list())
    app1$append_middleware(encode_decode_middleware)

    app2 = Application$new()

Here is example on how you can get the actual function used for `application/json` encoding:

    FUN = encode_decode_middleware$ContentHandlers$get_encode('application/json')
    FUN
    #> function(x, unbox = TRUE)  {
    #>   res = jsonlite::toJSON(x, dataframe = 'columns', auto_unbox = unbox, null = 'null', na = 'null')
    #>   unclass(res)
    #> }
    #> <bytecode: 0x7f93f3b58c40>
    #> <environment: namespace:RestRserve>

We can manually override application default content-type:

    application$add_get(path = "/factorial-json", function(.req, .res) {
      x = as.integer(.req$get_param_query("x"))
      result = factorial(x)
      .res$set_body(list(result = result))
      .res$set_content_type("application/json")
    })

    request = Request$new(
      path = "/factorial-json",
      method = "GET",
      parameters_query = list(x = 10)
    )
    response = application$process_request(request)

    response$body
    #> [1] "{\"result\":3628800}"

And here is a little bit more complex example where we store a binary object in the body. We will use R’s native serialization, but one can use `protobuf`, `messagepack`, etc.

    application$add_get(path = "/factorial-rds", function(.req, .res) {
      x = as.integer(.req$get_param_query("x"))
      result = factorial(x)
      body_rds = serialize(list(result = result), connection = NULL)
      .res$set_body(body_rds)
      .res$set_content_type("application/x-rds")
    })

However function above won’t work correctly. Out of the box `ContentHndlers` doesn’t know anything about `application/x-rds`:

    request = Request$new(
      path = "/factorial-rds",
      method = "GET",
      parameters_query = list(x = 10)
    )
    response = application$process_request(request)
    response$body
    #> [1] "500 Internal Server Error: can't encode body with content_type = 'application/x-rds'"

In order to resolve problem above we would need to either register `application/x-rds` content handler with `ContentHandlers$set_encode()` or manually specify `encode` function (`identity` in our case):

    application$add_get(path = "/factorial-rds2", function(.req, .res) {
      x = as.integer(.req$get_param_query("x"))
      result = factorial(x)
      body_rds = serialize(list(result = result), connection = NULL)
      .res$set_body(body_rds)
      .res$set_content_type("application/x-rds")
      .res$encode = identity
    })

Now the answer is valid:

    request = Request$new(
      path = "/factorial-rds2",
      method = "GET",
      parameters_query = list(x = 10)
    )
    response = application$process_request(request)
    unserialize(response$body)
    #> $result
    #> [1] 3628800

# Decoding

RestRserve facilitates with parsing incoming request body as well. Consider a service which expects JSON POST requests:

    application = Application$new(content_type = "application/json")
    application$add_post("/echo", function(.req, .res) {
      .res$set_body(.req$body)
    })

    request = Request$new(path = "/echo", method = "POST", body = '{"hello":"world"}', content_type = "application/json")
    response = application$process_request(request)
    response$body
    #> [1] "{\"hello\":\"world\"}"

The logic behind decoding is also controlled by ?EncodeDecodeMiddleware and its `ContentHandlers` property.

# Extending encoding and decoding

Here is an example which demonstrates on how to extend ?EncodeDecodeMiddleware to handle additional content types:

    encode_decode_middleware = EncodeDecodeMiddleware$new()

    encode_decode_middleware$ContentHandlers$set_encode(
      "text/csv",
      function(x) {
        con = rawConnection(raw(0), "w")
        on.exit(close(con))
        write.csv(x, con, row.names = FALSE)
        rawConnectionValue(con)
      }
    )

    encode_decode_middleware$ContentHandlers$set_decode(
      "text/csv",
      function(x) {
        res = try({
          con = textConnection(rawToChar(x), open = "r")
          on.exit(close(con))
          read.csv(con)
        }, silent = TRUE)

        if (inherits(res, "try-error")) {
          raise(HTTPError$bad_request(body = attributes(res)$condition$message))
        }
        return(res)
      }
    )

Extended middleware needs to be provided to the application constructor:

    data(iris)
    app = Application$new(middleware = list(encode_decode_middleware))

Now let’s test it:

    app$add_get("/iris", FUN = function(.req, .res) {
      .res$set_content_type("text/csv")
      .res$set_body(iris)
    })

    req = Request$new(path = "/iris", method = "GET")
    res = app$process_request(req)

    iris_out = read.csv(textConnection(rawToChar(res$body)))
    head(iris_out)
    #>   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
    #> 1          5.1         3.5          1.4         0.2  setosa
    #> 2          4.9         3.0          1.4         0.2  setosa
    #> 3          4.7         3.2          1.3         0.2  setosa
    #> 4          4.6         3.1          1.5         0.2  setosa
    #> 5          5.0         3.6          1.4         0.2  setosa
    #> 6          5.4         3.9          1.7         0.4  setosa

    app$add_post("/in", FUN = function(.req, .res) {
      str(.req$body)
    })
    req = Request$new(path = "/in", method = "POST", body = res$body, content_type = "text/csv")
    app$process_request(req)
    #> 'data.frame':    150 obs. of  5 variables:
    #>  $ Sepal.Length: num  5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
    #>  $ Sepal.Width : num  3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
    #>  $ Petal.Length: num  1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
    #>  $ Petal.Width : num  0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
    #>  $ Species     : chr  "setosa" "setosa" "setosa" "setosa" ...
    #> <RestRserve Response>
    #>   status code: 200 OK
    #>   content-type: text/plain
    #>   <Headers>
    #>     Server: RestRserve/1.2.4; Rserve/1.8.13