Merge: doc: fixed some typos and other misc. corrections
[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 #
17 # ~~~nitish
18 # import app::http_request
19 #
20 # class MyHttpRequest
21 # super AsyncHttpRequest
22 #
23 # redef fun uri do return "http://example.com/"
24 #
25 # redef fun on_load(data, status) do print "Received: {data or else "null"}"
26 #
27 # redef fun on_fail(error) do print "Connection error: {error}"
28 # end
29 #
30 # var req = new MyHttpRequest
31 # req.start
32 # ~~~
33 module http_request
34
35 import app_base
36 import pthreads
37 import json
38
39 import linux::http_request is conditional(linux)
40 import android::http_request is conditional(android)
41 import ios::http_request is conditional(ios)
42
43 redef class App
44 # Platform specific service to execute `task` on the main/UI thread
45 fun run_on_ui_thread(task: Task) is abstract
46 end
47
48 # Thread executing an HTTP request asynchronously
49 #
50 # The request is sent to `uri`.
51 # Either `uri`, or `uri_root` and `uri_tail`, must be set in subclasses.
52 #
53 # If `deserialize_json`, the default behavior, the response is deserialized from JSON
54 #
55 # If `delay > 0.0`, sending the request is delayed by the given `delay` in seconds.
56 # It can be used to delay resending a request on error.
57 #
58 # Four callback methods act on the main/UI thread,
59 # they should be implemented as needed in subclasses:
60 # * `before`
61 # * `on_load`
62 # * `on_fail`
63 # * `after`
64 #
65 # See full example at `examples/http_request_example.nit`.
66 abstract class AsyncHttpRequest
67 super Thread
68
69 # URI target of this request, by default it is composed of `uri_root / uri_tail`
70 fun uri: Text do return uri_root / uri_tail
71
72 # Root URI of the remote server, usually the scheme and remote host
73 fun uri_root: String is abstract
74
75 # Right part of the URI, after `uri_root`, often the resource path and the query
76 fun uri_tail: String do return ""
77
78 # Should the response content be deserialized from JSON?
79 var deserialize_json = true is writable
80
81 # Delay in seconds before sending this request
82 var delay = 0.0 is writable
83
84 redef fun start
85 do
86 before
87 super
88 end
89
90 redef fun main
91 do
92 var delay = delay
93 if delay > 0.0 then delay.sleep
94
95 var uri = uri
96
97 # Execute REST request
98 var rep = uri.http_get
99 if rep.is_error then
100 app.run_on_ui_thread new RestRunnableOnFail(self, rep.error)
101 return null
102 end
103
104 if deserialize_json then
105 # Deserialize
106 var deserializer = new JsonDeserializer(rep.value)
107 var res = deserializer.deserialize
108 if deserializer.errors.not_empty then
109 app.run_on_ui_thread new RestRunnableOnFail(self, deserializer.errors.first)
110 else
111 app.run_on_ui_thread new RestRunnableOnLoad(self, res, rep.code)
112 end
113 else
114 # Return text data
115 app.run_on_ui_thread new RestRunnableOnLoad(self, rep.value, rep.code)
116 return null
117 end
118
119 app.run_on_ui_thread new RestRunnableJoin(self)
120
121 return null
122 end
123
124 # Prepare the UI or other parts of the program before executing the REST request
125 fun before do end
126
127 # Invoked when the HTTP request returned valid data
128 #
129 # If `deserialize_json`, the default behavior, this method is invoked only if deserialization was successful.
130 # In this case, `result` may be any deserialized object.
131 #
132 # Otherwise, if `not deserialize_json`, `result` contains the content of the response as a `String`.
133 fun on_load(result: nullable Object, http_status_code: Int) do end
134
135 # Invoked when the HTTP request has failed and no data was received or deserialization failed
136 fun on_fail(error: Error) do print_error "HTTP request '{uri}' failed with: {error}"
137
138 # Complete this request whether it was a success or not
139 fun after do end
140 end
141
142 # Simple `AsyncHttpRequest` where `uri` is an attribute
143 #
144 # Prints on communication errors and when the remote server returns an
145 # HTTP status code not in the 200s.
146 #
147 # This class can be instantiated to execute a request where the response is
148 # ignored by the application. Alternatively, it can be subclassed to implement
149 # `on_load`.
150 #
151 # ~~~nitish
152 # var request = new SimpleAsyncHttpRequest("http://example.com")
153 # request.start
154 # ~~~
155 class SimpleAsyncHttpRequest
156 super AsyncHttpRequest
157
158 redef var uri
159
160 redef fun on_load(data, status) do if status < 200 or status >= 299
161 then print_error "HTTP request '{uri}' received HTTP status code: {status}"
162 end
163
164 redef class Text
165 # Execute an HTTP GET request synchronously at the URI `self`
166 #
167 # ~~~nitish
168 # var response = "http://example.org/".http_get
169 # if response.is_error then
170 # print_error response.error
171 # else
172 # print "HTTP status code: {response.code}"
173 # print response.value
174 # end
175 # ~~~
176 private fun http_get: HttpRequestResult is abstract
177 end
178
179 # Result of a call to `Text::http_get`
180 #
181 # Users should first check if `is_error` to use `error`.
182 # Otherwise they can use `value` to get the content of the response
183 # and `code` for the HTTP status code.
184 class HttpRequestResult
185 super MaybeError[String, Error]
186
187 # The HTTP status code, if any
188 var maybe_code: nullable Int
189
190 # The status code
191 #
192 # Require: `not is_error`
193 fun code: Int do return maybe_code.as(not null)
194 end
195
196 private abstract class HttpRequestTask
197 super Task
198
199 # `AsyncHttpRequest` to which send callbacks
200 var sender_thread: AsyncHttpRequest
201 end
202
203 private class RestRunnableOnLoad
204 super HttpRequestTask
205
206 var res: nullable Object
207
208 var code: Int
209
210 redef fun main
211 do
212 sender_thread.on_load(res, code)
213 sender_thread.after
214 end
215 end
216
217 private class RestRunnableOnFail
218 super HttpRequestTask
219
220 var error: Error
221
222 redef fun main
223 do
224 sender_thread.on_fail(error)
225 sender_thread.after
226 end
227 end
228
229 private class RestRunnableJoin
230 super HttpRequestTask
231
232 redef fun main do sender_thread.join
233 end