lib/app: intro the http_request API
authorAlexis Laferrière <alexis.laf@xymus.net>
Sat, 31 Oct 2015 23:54:57 +0000 (19:54 -0400)
committerAlexis Laferrière <alexis.laf@xymus.net>
Sun, 8 Nov 2015 17:33:11 +0000 (12:33 -0500)
Signed-off-by: Alexis Laferrière <alexis.laf@xymus.net>

lib/app/http_request.nit [new file with mode: 0644]

diff --git a/lib/app/http_request.nit b/lib/app/http_request.nit
new file mode 100644 (file)
index 0000000..70990a9
--- /dev/null
@@ -0,0 +1,161 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# HTTP request services: `AsyncHttpRequest` and `Text::http_get`
+module http_request
+
+import app_base
+import pthreads
+import json::serialization
+
+import linux::http_request is conditional(linux)
+import android::http_request is conditional(android)
+
+redef class App
+       # Platform specific service to execute `task` on the main/UI thread
+       fun run_on_ui_thread(task: Task) is abstract
+end
+
+# Thread executing an HTTP request and deserializing JSON asynchronously
+#
+# This class defines four methods acting on the main/UI thread,
+# they should be implemented as needed:
+# * before
+# * on_load
+# * on_fail
+# * after
+class AsyncHttpRequest
+       super Thread
+
+       # Root URI of the remote server
+       fun rest_server_uri: String is abstract
+
+       # Action, or path, for this request within the `rest_server_uri`
+       fun rest_action: String is abstract
+
+       # Should the response content be deserialized from JSON?
+       var deserialize_json = true is writable
+
+       redef fun start
+       do
+               before
+               super
+       end
+
+       redef fun main
+       do
+               var uri = rest_server_uri / rest_action
+
+               # Execute REST request
+               var rep = uri.http_get
+               if rep.is_error then
+                       app.run_on_ui_thread new RestRunnableOnFail(self, rep.error)
+                       return null
+               end
+
+               if not deserialize_json then
+                       app.run_on_ui_thread new RestRunnableOnLoad(self, rep)
+                       return null
+               end
+
+               # Deserialize
+               var deserializer = new JsonDeserializer(rep.value)
+               var res = deserializer.deserialize
+               if deserializer.errors.not_empty then
+                       app.run_on_ui_thread new RestRunnableOnFail(self, deserializer.errors.first)
+               end
+
+               app.run_on_ui_thread new RestRunnableOnLoad(self, res)
+               return null
+       end
+
+       # Prepare the UI or other parts of the program before executing the REST request
+       fun before do end
+
+       # Invoked when the HTTP request returned valid data
+       #
+       # If `deserialize_json`, the default behavior, this method is invoked only if deserialization was successful.
+       # In this case, `result` may be any deserialized object.
+       #
+       # Otherwise, if `not deserialize_json`, `result` contains the content of the response as a `String`.
+       fun on_load(result: nullable Object) do end
+
+       # Invoked when the HTTP request has failed and no data was received or deserialization failed
+       fun on_fail(error: Error) do print_error "REST request '{rest_action}' failed with: {error}"
+
+       # Complete this request whether it was a success or not
+       fun after do end
+end
+
+redef class Text
+       # Execute an HTTP GET request synchronously at the URI `self`
+       #
+       # ~~~nitish
+       # var response = "http://example.org/".http_get
+       # if response.is_error then
+       #     print_error response.error
+       # else
+       #     print "HTTP status code: {response.code}"
+       #     print response.value
+       # end
+       # ~~~
+       private fun http_get: HttpRequestResult is abstract
+end
+
+# Result of a call to `Text::http_get`
+#
+# Users should first check if `is_error` to use `error`.
+# Otherwise they can use `value` to get the content of the response
+# and `code` for the HTTP status code.
+class HttpRequestResult
+       super MaybeError[String, Error]
+
+       # The HTTP status code, if any
+       var maybe_code: nullable Int
+
+       # The status code
+       # Require: `not is_error`
+       fun code: Int do return maybe_code.as(not null)
+end
+
+private abstract class HttpRequestTask
+       super Task
+
+       # `AsyncHttpRequest` to which send callbacks
+       var sender_thread: AsyncHttpRequest
+end
+
+private class RestRunnableOnLoad
+       super HttpRequestTask
+
+       var res: nullable Object
+
+       redef fun main
+       do
+               sender_thread.on_load(res)
+               sender_thread.after
+       end
+end
+
+private class RestRunnableOnFail
+       super HttpRequestTask
+
+       var error: Error
+
+       redef fun main
+       do
+               sender_thread.on_fail(error)
+               sender_thread.after
+       end
+end