lib/app: intro the http_request API
[nit.git] / lib / app / http_request.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 # HTTP request services: `AsyncHttpRequest` and `Text::http_get`
16 module http_request
17
18 import app_base
19 import pthreads
20 import json::serialization
21
22 import linux::http_request is conditional(linux)
23 import android::http_request is conditional(android)
24
25 redef class App
26 # Platform specific service to execute `task` on the main/UI thread
27 fun run_on_ui_thread(task: Task) is abstract
28 end
29
30 # Thread executing an HTTP request and deserializing JSON asynchronously
31 #
32 # This class defines four methods acting on the main/UI thread,
33 # they should be implemented as needed:
34 # * before
35 # * on_load
36 # * on_fail
37 # * after
38 class AsyncHttpRequest
39 super Thread
40
41 # Root URI of the remote server
42 fun rest_server_uri: String is abstract
43
44 # Action, or path, for this request within the `rest_server_uri`
45 fun rest_action: String is abstract
46
47 # Should the response content be deserialized from JSON?
48 var deserialize_json = true is writable
49
50 redef fun start
51 do
52 before
53 super
54 end
55
56 redef fun main
57 do
58 var uri = rest_server_uri / rest_action
59
60 # Execute REST request
61 var rep = uri.http_get
62 if rep.is_error then
63 app.run_on_ui_thread new RestRunnableOnFail(self, rep.error)
64 return null
65 end
66
67 if not deserialize_json then
68 app.run_on_ui_thread new RestRunnableOnLoad(self, rep)
69 return null
70 end
71
72 # Deserialize
73 var deserializer = new JsonDeserializer(rep.value)
74 var res = deserializer.deserialize
75 if deserializer.errors.not_empty then
76 app.run_on_ui_thread new RestRunnableOnFail(self, deserializer.errors.first)
77 end
78
79 app.run_on_ui_thread new RestRunnableOnLoad(self, res)
80 return null
81 end
82
83 # Prepare the UI or other parts of the program before executing the REST request
84 fun before do end
85
86 # Invoked when the HTTP request returned valid data
87 #
88 # If `deserialize_json`, the default behavior, this method is invoked only if deserialization was successful.
89 # In this case, `result` may be any deserialized object.
90 #
91 # Otherwise, if `not deserialize_json`, `result` contains the content of the response as a `String`.
92 fun on_load(result: nullable Object) do end
93
94 # Invoked when the HTTP request has failed and no data was received or deserialization failed
95 fun on_fail(error: Error) do print_error "REST request '{rest_action}' failed with: {error}"
96
97 # Complete this request whether it was a success or not
98 fun after do end
99 end
100
101 redef class Text
102 # Execute an HTTP GET request synchronously at the URI `self`
103 #
104 # ~~~nitish
105 # var response = "http://example.org/".http_get
106 # if response.is_error then
107 # print_error response.error
108 # else
109 # print "HTTP status code: {response.code}"
110 # print response.value
111 # end
112 # ~~~
113 private fun http_get: HttpRequestResult is abstract
114 end
115
116 # Result of a call to `Text::http_get`
117 #
118 # Users should first check if `is_error` to use `error`.
119 # Otherwise they can use `value` to get the content of the response
120 # and `code` for the HTTP status code.
121 class HttpRequestResult
122 super MaybeError[String, Error]
123
124 # The HTTP status code, if any
125 var maybe_code: nullable Int
126
127 # The status code
128 # Require: `not is_error`
129 fun code: Int do return maybe_code.as(not null)
130 end
131
132 private abstract class HttpRequestTask
133 super Task
134
135 # `AsyncHttpRequest` to which send callbacks
136 var sender_thread: AsyncHttpRequest
137 end
138
139 private class RestRunnableOnLoad
140 super HttpRequestTask
141
142 var res: nullable Object
143
144 redef fun main
145 do
146 sender_thread.on_load(res)
147 sender_thread.after
148 end
149 end
150
151 private class RestRunnableOnFail
152 super HttpRequestTask
153
154 var error: Error
155
156 redef fun main
157 do
158 sender_thread.on_fail(error)
159 sender_thread.after
160 end
161 end