-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Non-Volley HTTP Requests See merge request 415-cradle/cradlemobile!49
- Loading branch information
Showing
4 changed files
with
331 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
package com.cradle.neptune.net | ||
|
||
import com.cradle.neptune.model.Marshal | ||
import java.lang.Exception | ||
import java.net.HttpURLConnection | ||
import java.net.URL | ||
|
||
/** | ||
* Contains functions for making generic HTTP requests. | ||
*/ | ||
object Http { | ||
|
||
/** | ||
* Enumeration of common HTTP method request types. | ||
*/ | ||
enum class Method { GET, POST, PUT, DELETE } | ||
|
||
/** | ||
* Performs a generic HTTP request. | ||
* | ||
* @param method the request method | ||
* @param url where to send the request | ||
* @param headers HTTP headers to include with the request | ||
* @param body an optional body to send along with the request | ||
* @return the result of the network request | ||
* @throws java.net.MalformedURLException if [url] is malformed | ||
*/ | ||
fun request( | ||
method: Method, | ||
url: String, | ||
headers: Map<String, String>, | ||
body: ByteArray? | ||
): NetworkResult<ByteArray> = | ||
with(URL(url).openConnection() as HttpURLConnection) { | ||
requestMethod = method.toString() | ||
headers.forEach { (k, v) -> addRequestProperty(k, v) } | ||
doInput = true | ||
|
||
try { | ||
if (body != null) { | ||
doOutput = true | ||
outputStream.write(body) | ||
} | ||
|
||
@Suppress("MagicNumber") | ||
if (responseCode in 200 until 300) { | ||
val responseBody = inputStream.readBytes() | ||
inputStream.close() | ||
Success(responseBody, responseCode) | ||
} else { | ||
val responseBody = errorStream.readBytes() | ||
errorStream.close() | ||
Failure(responseBody, responseCode) | ||
} | ||
} catch (ex: Exception) { | ||
NetworkException(ex) | ||
} | ||
} | ||
|
||
/** | ||
* Sends a generic HTTP request with a JSON body and expects a JSON | ||
* response. | ||
* | ||
* The "Content-Type application/json" header is automatically included | ||
* in requests sent using this function. | ||
* | ||
* @param method the request method | ||
* @param url where to send the request | ||
* @param headers HTTP headers to include with the request | ||
* @param body an optional body to send along with the request | ||
* @return the result of the network request | ||
* @throws java.net.MalformedURLException if [url] is malformed | ||
* @throws org.json.JSONException if the response body is not JSON | ||
*/ | ||
fun jsonRequest( | ||
method: Method, | ||
url: String, | ||
headers: Map<String, String>, | ||
body: Json? | ||
): NetworkResult<Json> = | ||
request( | ||
method, | ||
url, | ||
headers + ("Content-Type" to "application/json"), | ||
body?.marshal() | ||
).map(Json.Companion::unmarshal) | ||
|
||
/** | ||
* A generalized version of [jsonRequest] which accepts a generic instance | ||
* for the [body] parameter. | ||
* | ||
* Useful for POST requests where you don't care about the response body. | ||
* | ||
* @param method the request method | ||
* @param url where to send the request | ||
* @param headers HTTP headers to include with the request | ||
* @param body an optional body to send along with the request | ||
* @return the result of the network request | ||
* @throws java.net.MalformedURLException if [url] is malformed | ||
* @throws org.json.JSONException if the response body is not JSON | ||
*/ | ||
fun <Body> jsonRequest( | ||
method: Method, | ||
url: String, | ||
headers: Map<String, String>, | ||
body: Body? | ||
): NetworkResult<Json> | ||
where Body : Marshal<Json> = | ||
jsonRequest(method, url, headers, body?.marshal()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
package com.cradle.neptune.net | ||
|
||
import com.cradle.neptune.model.Marshal | ||
import com.cradle.neptune.model.Unmarshal | ||
import org.json.JSONArray | ||
import org.json.JSONObject | ||
|
||
/** | ||
* Sum type which represents a blob of JSON data. | ||
*/ | ||
sealed class Json : Marshal<ByteArray> { | ||
|
||
/** | ||
* Unwraps this JSON data as an object. | ||
* | ||
* Returns `null` if this is a [JsonArray] and not a [JsonObject]. | ||
*/ | ||
val obj: JSONObject? | ||
get() = when (this) { | ||
is JsonObject -> value | ||
is JsonArray -> null | ||
} | ||
|
||
/** | ||
* Unwraps this JSON data as an array. | ||
* | ||
* Returns `null` if this is a [JsonObject] and not a [JsonArray] | ||
*/ | ||
val arr: JSONArray? | ||
get() = when (this) { | ||
is JsonObject -> null | ||
is JsonArray -> value | ||
} | ||
|
||
/** | ||
* Converts this JSON data into a string without any line breaks or | ||
* indentations. | ||
* | ||
* @return a string representation of the JSON data | ||
*/ | ||
abstract override fun toString(): String | ||
|
||
/** | ||
* Converts this JSON data into a string. | ||
* | ||
* @param indentFactor the number of spaces to used when indenting nested | ||
* structures | ||
* @return a string representation of the JSON data | ||
*/ | ||
abstract fun toString(indentFactor: Int): String | ||
|
||
/** | ||
* Converts this JSON data into a byte array. | ||
* | ||
* @return the JSON data as a byte array | ||
*/ | ||
final override fun marshal() = toString().toByteArray() | ||
|
||
companion object : Unmarshal<Json, ByteArray> { | ||
/** | ||
* Converts a byte array into a [JsonObject] or [JsonArray]. | ||
* | ||
* @param data the byte array to parse | ||
* @return a [Json] variant | ||
* @throws org.json.JSONException if unable to parse [data] | ||
*/ | ||
override fun unmarshal(data: ByteArray): Json { | ||
val str = String(data).trimStart() | ||
return if (str.firstOrNull() == '{') { | ||
JsonObject(str) | ||
} else { | ||
JsonArray(str) | ||
} | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Wraps a [JSONObject] in a [Json] variant allowing polymorphism between the | ||
* two JSON types. | ||
* | ||
* @property value Underlying [JSONObject] value | ||
*/ | ||
class JsonObject(val value: JSONObject) : Json() { | ||
|
||
constructor() : this(JSONObject()) | ||
|
||
constructor(string: String) : this(JSONObject(string)) | ||
|
||
override fun toString() = value.toString() | ||
|
||
override fun toString(indentFactor: Int): String = value.toString(indentFactor) | ||
} | ||
|
||
/** | ||
* Wraps a [JSONArray] in a [Json] variant allowing polymorphism between the | ||
* two JSON types. | ||
* | ||
* @property value Underlying [JSONArray] value | ||
*/ | ||
class JsonArray(val value: JSONArray) : Json() { | ||
|
||
constructor() : this(JSONArray()) | ||
|
||
constructor(string: String) : this(JSONArray(string)) | ||
|
||
override fun toString() = value.toString() | ||
|
||
override fun toString(indentFactor: Int): String = value.toString(indentFactor) | ||
} |
110 changes: 110 additions & 0 deletions
110
app/src/main/java/com/cradle/neptune/net/NetworkResult.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
package com.cradle.neptune.net | ||
|
||
import com.cradle.neptune.model.Unmarshal | ||
|
||
/** | ||
* Sum type representing the result of a network request. | ||
*/ | ||
sealed class NetworkResult<T> { | ||
/** | ||
* Unwraps this network result into an optional value. | ||
* | ||
* @return the result value or null depending on whether the result is a | ||
* [Success], [Failure], [NetworkException] variant | ||
*/ | ||
val unwrapped: T? | ||
get() = when (this) { | ||
is Success -> value | ||
is Failure -> null | ||
is NetworkException -> null | ||
} | ||
|
||
/** | ||
* Applies a closure [f] to transform the value field of a [Success] result. | ||
* | ||
* In the case of [Failure] and [NetworkException] variants, this method | ||
* does nothing. | ||
* | ||
* @param f transformation to apply to the result value | ||
* @return a new [NetworkResult] with the transformed value | ||
*/ | ||
fun <U> map(f: (T) -> U): NetworkResult<U> = when (this) { | ||
is Success -> Success(f(value), statusCode) | ||
is Failure -> Failure(body, statusCode) | ||
is NetworkException -> NetworkException(cause) | ||
} | ||
} | ||
|
||
/** | ||
* The result of a successful network request. | ||
* | ||
* A request is considered successful if the response has a status code in the | ||
* 200..<300 range. | ||
* | ||
* @property value The result value | ||
* @property statusCode Status code of the response which generated this result | ||
*/ | ||
data class Success<T>(val value: T, val statusCode: Int) : NetworkResult<T>() | ||
|
||
/** | ||
* The result of a network request which made it to the server but the status | ||
* code of the response indicated a failure (e.g., 404, 500, etc.). | ||
* | ||
* Contains the response status code along with the response body as a byte | ||
* array. Note that the body is not of type [T] like in [Success] since the | ||
* response for a failed request may not be the same type as the response for | ||
* a successful request. | ||
* | ||
* @property body The body of the response | ||
* @property statusCode The status code of the response | ||
*/ | ||
data class Failure<T>(val body: ByteArray, val statusCode: Int) : NetworkResult<T>() { | ||
|
||
/** | ||
* Converts the response body of this failure result to some other type. | ||
* | ||
* @param unmarshaller an object used to unmarshall the byte array body | ||
* into a different type | ||
* @return a new object which was constructed from the response body | ||
*/ | ||
fun <R, U> marshal(unmarshaller: U) | ||
where U : Unmarshal<R, ByteArray> = | ||
unmarshaller.unmarshal(body) | ||
|
||
/** | ||
* Converts the response body of this failure result to JSON. | ||
* | ||
* Whether a [JsonObject] or [JsonArray] is returned depends on the content | ||
* of the response body. | ||
* | ||
* @return a [Json] object | ||
* @throws org.json.JSONException if the response body cannot be converted | ||
* into JSON. | ||
*/ | ||
fun toJson() = marshal(Json.Companion) | ||
|
||
override fun equals(other: Any?): Boolean { | ||
if (this === other) return true | ||
if (javaClass != other?.javaClass) return false | ||
|
||
other as Failure<*> | ||
|
||
if (!body.contentEquals(other.body)) return false | ||
if (statusCode != other.statusCode) return false | ||
|
||
return true | ||
} | ||
|
||
override fun hashCode(): Int { | ||
var result = body.contentHashCode() | ||
result = 31 * result + statusCode | ||
return result | ||
} | ||
} | ||
|
||
/** | ||
* Represents an exception that occurred whilst making a network request. | ||
* | ||
* @property cause the exception which caused the failure | ||
*/ | ||
data class NetworkException<T>(val cause: Exception) : NetworkResult<T>() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters