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