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