Skip to content

Commit

Permalink
feat: add Android and TS code for raw request
Browse files Browse the repository at this point in the history
  • Loading branch information
edeckers committed Dec 29, 2021
1 parent e5b8e0e commit 4823491
Show file tree
Hide file tree
Showing 8 changed files with 317 additions and 2 deletions.
39 changes: 39 additions & 0 deletions android/src/main/java/io/deckers/blob_courier/BlobCourierModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import io.deckers.blob_courier.react.CongestionAvoidingProgressNotifierFactory
import io.deckers.blob_courier.react.processUnexpectedError
import io.deckers.blob_courier.react.processUnexpectedException
import io.deckers.blob_courier.react.toReactMap
import io.deckers.blob_courier.send.BlobSender
import io.deckers.blob_courier.send.SenderParameterFactory
import io.deckers.blob_courier.upload.BlobUploader
import io.deckers.blob_courier.upload.UploaderParameterFactory
import java.net.UnknownHostException
Expand Down Expand Up @@ -134,6 +136,43 @@ class BlobCourierModule(private val reactContext: ReactApplicationContext) :
li("Called fetchBlob")
}

@ReactMethod
fun sendBlob(input: ReadableMap, promise: Promise) {
li("Calling sendBlob")
thread {
try {
SenderParameterFactory()
.fromInput(input)
.fold(::Failure, ::Success)
.fmap(
BlobSender(
reactContext,
createHttpClient(),
createProgressFactory(reactContext)
)::send
)
.map { it.toReactMap() }
.`do`(
{ f ->
lv("Something went wrong during send (code=${f.code},message=${f.message})")
promise.reject(f.code, f.message)
},
promise::resolve
)
} catch (e: UnknownHostException) {
lv("Unknown host", e)
promise.reject(ERROR_UNKNOWN_HOST, e)
} catch (e: Exception) {
le("Unexpected exception", e)
promise.reject(ERROR_UNEXPECTED_EXCEPTION, processUnexpectedException(e).message)
} catch (e: Error) {
le("Unexpected error", e)
promise.reject(ERROR_UNEXPECTED_ERROR, processUnexpectedError(e).message)
}
}
li("Called sendBlob")
}

@ReactMethod
fun uploadBlob(input: ReadableMap, promise: Promise) {
li("Calling uploadBlob")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This source code is licensed under the MPL-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
package io.deckers.blob_courier.upload
package io.deckers.blob_courier.common

import android.content.ContentResolver
import android.net.Uri
Expand Down
87 changes: 87 additions & 0 deletions android/src/main/java/io/deckers/blob_courier/send/BlobSender.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/**
* Copyright (c) Ely Deckers.
*
* This source code is licensed under the MPL-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
package io.deckers.blob_courier.send

import android.content.Context
import io.deckers.blob_courier.cancel.registerCancellationHandler
import io.deckers.blob_courier.common.ERROR_CANCELED_EXCEPTION
import io.deckers.blob_courier.common.ERROR_UNEXPECTED_ERROR
import io.deckers.blob_courier.common.ERROR_UNEXPECTED_EXCEPTION
import io.deckers.blob_courier.common.Failure
import io.deckers.blob_courier.common.Logger
import io.deckers.blob_courier.common.Result
import io.deckers.blob_courier.common.Success
import io.deckers.blob_courier.common.createErrorFromThrowable
import io.deckers.blob_courier.common.mapHeadersToMap
import io.deckers.blob_courier.progress.BlobCourierProgressRequest
import io.deckers.blob_courier.progress.ProgressNotifierFactory
import okhttp3.OkHttpClient
import okhttp3.Request
import java.io.IOException

private val TAG = BlobSender::class.java.name

private val logger = Logger(TAG)
private fun li(m: String) = logger.i(m)

class BlobSender(
private val context: Context,
private val httpClient: OkHttpClient,
private val progressNotifierFactory: ProgressNotifierFactory
) {

fun send(senderParameters: SenderParameters): Result<Map<String, Any>> {
li("Starting unmanaged send")

val requestBody = BlobCourierProgressRequest(
senderParameters.toRequestBody(context.contentResolver),
progressNotifierFactory.create(senderParameters.taskId)
)

val requestBuilder = Request.Builder()
.url(senderParameters.uri)
.method(senderParameters.method, requestBody)
.apply {
senderParameters.headers.forEach { e: Map.Entry<String, String> ->
addHeader(e.key, e.value)
}
}
.build()

val sendRequestCall = httpClient.newCall(requestBuilder)

try {
registerCancellationHandler(context, senderParameters.taskId, sendRequestCall)

val response = sendRequestCall.execute()

val responseBody = response.body()?.string().orEmpty()

li("Finished unmanaged send")

return Success(
mapOf(
"response" to mapOf(
"code" to response.code(),
"data" to if (senderParameters.returnResponse) responseBody else "",
"headers" to mapHeadersToMap(response.headers())
)
)
)
} catch (e: IOException) {
if (sendRequestCall.isCanceled) {
return Failure(createErrorFromThrowable(ERROR_CANCELED_EXCEPTION, e))
}

return Failure(createErrorFromThrowable(ERROR_UNEXPECTED_EXCEPTION, e))
} catch (e: Exception) {
return Failure(createErrorFromThrowable(ERROR_UNEXPECTED_EXCEPTION, e))
} catch (e: Error) {
return Failure(createErrorFromThrowable(ERROR_UNEXPECTED_ERROR, e))
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/**
* Copyright (c) Ely Deckers.
*
* This source code is licensed under the MPL-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
package io.deckers.blob_courier.send

import android.content.ContentResolver
import android.net.Uri
import com.facebook.react.bridge.ReadableMap
import io.deckers.blob_courier.common.DEFAULT_MIME_TYPE
import io.deckers.blob_courier.common.DEFAULT_PROGRESS_TIMEOUT_MILLISECONDS
import io.deckers.blob_courier.common.DEFAULT_UPLOAD_METHOD
import io.deckers.blob_courier.common.InputStreamRequestBody
import io.deckers.blob_courier.common.PARAMETER_ABSOLUTE_FILE_PATH
import io.deckers.blob_courier.common.PARAMETER_HEADERS
import io.deckers.blob_courier.common.PARAMETER_METHOD
import io.deckers.blob_courier.common.PARAMETER_MIME_TYPE
import io.deckers.blob_courier.common.PARAMETER_SETTINGS_PROGRESS_INTERVAL
import io.deckers.blob_courier.common.PARAMETER_TASK_ID
import io.deckers.blob_courier.common.PARAMETER_URL
import io.deckers.blob_courier.common.PROVIDED_PARAMETERS
import io.deckers.blob_courier.common.ValidationResult
import io.deckers.blob_courier.common.ValidationSuccess
import io.deckers.blob_courier.common.filterHeaders
import io.deckers.blob_courier.common.getMapInt
import io.deckers.blob_courier.common.hasRequiredStringField
import io.deckers.blob_courier.common.ifNone
import io.deckers.blob_courier.common.isNotNull
import io.deckers.blob_courier.common.maybe
import io.deckers.blob_courier.common.right
import io.deckers.blob_courier.common.testKeep
import io.deckers.blob_courier.common.validationContext
import io.deckers.blob_courier.upload.FilePayload
import io.deckers.blob_courier.upload.StringPayload
import io.deckers.blob_courier.upload.UploaderParameters
import okhttp3.MediaType
import okhttp3.MultipartBody
import okhttp3.RequestBody
import java.net.URL

private const val PARAMETER_RETURN_RESPONSE = "returnResponse"

data class RequiredParameters(
val taskId: String,
val url: String,
val absoluteFilePath: String,
)

data class SenderParameters(
val absoluteFilePath: Uri,
val mediaType: String,
val method: String,
val headers: Map<String, String>,
val progressInterval: Int,
val returnResponse: Boolean,
val taskId: String,
val uri: URL,
)

fun SenderParameters.toRequestBody(contentResolver: ContentResolver): RequestBody =
InputStreamRequestBody(
mediaType.let(MediaType::parse)
?: MediaType.get(DEFAULT_MIME_TYPE),
contentResolver,
absoluteFilePath
)

private fun verifyRequiredParametersProvided(input: ReadableMap):
ValidationResult<RequiredParameters> =
validationContext(input, isNotNull(PROVIDED_PARAMETERS))
.fmap(testKeep(hasRequiredStringField(PARAMETER_ABSOLUTE_FILE_PATH)))
.fmap(testKeep(hasRequiredStringField(PARAMETER_TASK_ID)))
.fmap(testKeep(hasRequiredStringField(PARAMETER_URL)))
.fmap { (_, validatedParameters) ->
val (url, rest) = validatedParameters
val (taskId, rest2) = rest
val (absoluteFilePath, _) = rest2

ValidationSuccess(RequiredParameters(taskId, url, absoluteFilePath))
}

class SenderParameterFactory {
fun fromInput(input: ReadableMap): ValidationResult<SenderParameters> =
verifyRequiredParametersProvided(input)
.fmap {
val (taskId, url, absoluteFilePath) = it

val mediaType = maybe(input.getString(PARAMETER_MIME_TYPE)).ifNone(DEFAULT_MIME_TYPE)
val method = maybe(input.getString(PARAMETER_METHOD)).ifNone(DEFAULT_UPLOAD_METHOD)

val unfilteredHeaders =
input.getMap(PARAMETER_HEADERS)?.toHashMap() ?: emptyMap<String, Any>()

val headers = filterHeaders(unfilteredHeaders)

val returnResponse =
input.hasKey(PARAMETER_RETURN_RESPONSE) && input.getBoolean(PARAMETER_RETURN_RESPONSE)

val progressInterval =
getMapInt(
input,
PARAMETER_SETTINGS_PROGRESS_INTERVAL,
DEFAULT_PROGRESS_TIMEOUT_MILLISECONDS
)

right(SenderParameters(
Uri.parse(absoluteFilePath),
mediaType,
method,
headers,
progressInterval,
returnResponse,
taskId,
URL(url),
))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import io.deckers.blob_courier.common.DEFAULT_MIME_TYPE
import io.deckers.blob_courier.common.DEFAULT_PROGRESS_TIMEOUT_MILLISECONDS
import io.deckers.blob_courier.common.DEFAULT_UPLOAD_METHOD
import io.deckers.blob_courier.common.Either
import io.deckers.blob_courier.common.InputStreamRequestBody
import io.deckers.blob_courier.common.PARAMETER_ABSOLUTE_FILE_PATH
import io.deckers.blob_courier.common.PARAMETER_FILENAME
import io.deckers.blob_courier.common.PARAMETER_HEADERS
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import io.deckers.blob_courier.common.validate
import io.deckers.blob_courier.react.CongestionAvoidingProgressNotifier
import io.deckers.blob_courier.react.CongestionAvoidingProgressNotifierFactory
import io.deckers.blob_courier.react.toReactMap
import io.deckers.blob_courier.upload.InputStreamRequestBody
import io.deckers.blob_courier.common.InputStreamRequestBody
import io.deckers.blob_courier.upload.UploaderParameterFactory
import io.deckers.blob_courier.upload.toMultipartBody
import io.mockk.EqMatcher
Expand Down
10 changes: 10 additions & 0 deletions src/ExposedTypes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,14 @@ export declare interface BlobUploadRequest
readonly multipartName?: string;
}

export declare interface BlobSendRequest
extends BlobBaseRequest,
BlobRequestMimeType,
BlobRequestMethod,
BlobRequestReturnResponse {
readonly absoluteFilePath: string;
}

export declare interface BlobMultipartBaseRequest
extends BlobBaseRequest,
BlobRequestMethod,
Expand Down Expand Up @@ -162,4 +170,6 @@ export type BlobFetchInput = BlobFetchRequest &
AndroidFetchSettings &
IOSFetchSettings;

export type BlobSendInput = BlobSendRequest & BlobRequestSettings;

export type BlobUploadInput = BlobUploadRequest & BlobRequestSettings;
Loading

0 comments on commit 4823491

Please sign in to comment.