-
Notifications
You must be signed in to change notification settings - Fork 269
REST Client
Wiki ▸ Documentation ▸ REST Client
Tornado FX comes with a REST Client built on top of Apache HttpClient 4. The actual HttpClient is not hidden from you, so you can use all the bells and whistles you are used to, but Tornado FX adds functionality spesific to REST communication in the form of extension functions.
If you don't use the REST Client in your application, feel free to exclude the httpclient
and javax.json
dependencies from your project descriptor.
If you mostly access the same api on every call, you can set a base uri so subsequent calls only need to include relative urls. You can configure the base url anywhere you like, but the init
function of your App
class is a good place to do it.
class MyApp : App() {
init {
val api = find(Rest::class)
api.baseURI = "http://contoso.com/api"
}
}
Inject the Rest
controller into your controllers in the [normal way](Dependency Injection).
There are convenience functions to perform GET
, PUT
, POST
and DELETE
operations.
class CustomerController : Controller() {
val api = Rest by inject()
fun loadCustomers(): ObservableList<Customer> =
api.get("customers").list().toModel()
}
CustomerController with loadCustomers call
So, what exactly is going on in the loadCustomers
function? First we call api.get("customers")
which will perform the call and return a HttpResponse
object from the Apache HttpClient. We then call the extension function HttpResponse.list()
which will consume the response and convert it to a javax.json.JsonArray
. Lastly, we call the extension function JsonArray.toModel()
which creates one Customer
object per JsonObject
in the array and calls JsonModel.updateModel
on it. In this example, the type argument is taken from the function return type, but you could also write the above method like this if you prefer:
fun loadCustomers() = api.get("customers").list().toModel(Customer::class)
How you provide the type argument to the toModel
function is a matter of taste, so choose the syntax you are most comfortable with.
With the exception of GET
, these functions take an optional parameter with either a JSON
object or a JsonModel
object that will be the payload of your request, converted to a JSON string.
The following example updates a customer object.
fun updateCustomer(customer: Customer) = api.put("customers/${customer.id}", customer)
If the api endpoint returns the customer object to us after save, we would simply append a toModel
call at the end of the function.
fun updateCustomer(customer: Customer) = api.put("customers/${customer.id}", customer)
.toModel(Customer::class)
If an I/O error occurs during the processing of the request, the default Error Handler will report the error to the user. You can ofcourse catch any errors yourself instead. To handle HTTP return codes, you might want to inspect the HttpResponse
before you convert the result to JSON.
fun getCustomer(id: Int): Customer {
val response = api.get("some/action")
if (response.ok())
return response.one().toModel()
else if (response.statusCode == 404)
throw CustomerNotFound()
else
throw HttpError("getCustomer returned ${response.statusCode} ${response.reason}")
}
Extract status code and reason from
HttpResponse
response.ok()
is shorthand for response.statusCode == 200
. You can also access any other property of the response in the normal way.
Tornado FX makes it very easy to add basic authentication to your api requests:
api.setBasicAuth("username", "password")
You can also directly access the HttpClient by through api.client
, or call api.configure()' where you can supply a function that acts upon the supplied
HttpClientBuilder`. For advanced configuration, please see the Apache HttpClient documentation.
You can for example show a login screen if an HTTP call fails with statusCode 401:
api.configure {
it.addInterceptorLast { response: HttpResponse, context: HttpContext ->
if (response.statusCode == 401)
showLoginScreen("Invalid credentials, please log in again.")
}
}
You can create multiple instances of the Rest
class by subclassing it and configuring each subclass as you wish. Injection of subclasses work seamlessly.
If you do multiple http calls they will not be pooled and returned in the order you executed the calls. Any http request will return as soon as it is available. If you want to handle them in sequence, or even discard older results, you can use the HttpResponse.seq()
function which will contain a Long
sequence number.
Tornado FX comes with a HTTP Client ProgressIndicator View. This view can be embedded in your application and will show you information about ongoing REST calls. Simply embed the RestProgressBar
into a ToolBar or any other parent container:
toolbar.add(RestProgressBar::class)
Next: i18n