Merge: Added contributing guidelines and link from readme
[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 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 `rest_server_uri / rest_action`.
34 #
35 # If `deserialize_json`, the default behavior, the response is deserialized from JSON
36 #
37 # If `delay > 0.0`, sending the reqest is delayed by the given `delay` in seconds.
38 # It can be used to delay resending a request on error.
39 #
40 # Four callback methods act on the main/UI thread,
41 # they should be implemented as needed in subclasses:
42 # * `before`
43 # * `on_load`
44 # * `on_fail`
45 # * `after`
46 class AsyncHttpRequest
47 super Thread
48
49 # Root URI of the remote server
50 fun rest_server_uri: String is abstract
51
52 # Action, or path, for this request within the `rest_server_uri`
53 fun rest_action: String is abstract
54
55 # Should the response content be deserialized from JSON?
56 var deserialize_json = true is writable
57
58 # Delay in seconds before sending this request
59 var delay = 0.0 is writable
60
61 redef fun start
62 do
63 before
64 super
65 end
66
67 redef fun main
68 do
69 var delay = delay
70 if delay > 0.0 then delay.sleep
71
72 var uri = rest_server_uri / rest_action
73
74 # Execute REST request
75 var rep = uri.http_get
76 if rep.is_error then
77 app.run_on_ui_thread new RestRunnableOnFail(self, rep.error)
78 return null
79 end
80
81 if not deserialize_json then
82 app.run_on_ui_thread new RestRunnableOnLoad(self, rep)
83 return null
84 end
85
86 # Deserialize
87 var deserializer = new JsonDeserializer(rep.value)
88 var res = deserializer.deserialize
89 if deserializer.errors.not_empty then
90 app.run_on_ui_thread new RestRunnableOnFail(self, deserializer.errors.first)
91 end
92
93 app.run_on_ui_thread new RestRunnableOnLoad(self, res)
94 return null
95 end
96
97 # Prepare the UI or other parts of the program before executing the REST request
98 fun before do end
99
100 # Invoked when the HTTP request returned valid data
101 #
102 # If `deserialize_json`, the default behavior, this method is invoked only if deserialization was successful.
103 # In this case, `result` may be any deserialized object.
104 #
105 # Otherwise, if `not deserialize_json`, `result` contains the content of the response as a `String`.
106 fun on_load(result: nullable Object) do end
107
108 # Invoked when the HTTP request has failed and no data was received or deserialization failed
109 fun on_fail(error: Error) do print_error "REST request '{rest_action}' failed with: {error}"
110
111 # Complete this request whether it was a success or not
112 fun after do end
113 end
114
115 redef class Text
116 # Execute an HTTP GET request synchronously at the URI `self`
117 #
118 # ~~~nitish
119 # var response = "http://example.org/".http_get
120 # if response.is_error then
121 # print_error response.error
122 # else
123 # print "HTTP status code: {response.code}"
124 # print response.value
125 # end
126 # ~~~
127 private fun http_get: HttpRequestResult is abstract
128 end
129
130 # Result of a call to `Text::http_get`
131 #
132 # Users should first check if `is_error` to use `error`.
133 # Otherwise they can use `value` to get the content of the response
134 # and `code` for the HTTP status code.
135 class HttpRequestResult
136 super MaybeError[String, Error]
137
138 # The HTTP status code, if any
139 var maybe_code: nullable Int
140
141 # The status code
142 # Require: `not is_error`
143 fun code: Int do return maybe_code.as(not null)
144 end
145
146 private abstract class HttpRequestTask
147 super Task
148
149 # `AsyncHttpRequest` to which send callbacks
150 var sender_thread: AsyncHttpRequest
151 end
152
153 private class RestRunnableOnLoad
154 super HttpRequestTask
155
156 var res: nullable Object
157
158 redef fun main
159 do
160 sender_thread.on_load(res)
161 sender_thread.after
162 end
163 end
164
165 private class RestRunnableOnFail
166 super HttpRequestTask
167
168 var error: Error
169
170 redef fun main
171 do
172 sender_thread.on_fail(error)
173 sender_thread.after
174 end
175 end