Implement Cloudinary Signed Upload in Kotlin - kotlin

I am having a tough time in implementing signed upload to Cloudinary using Kotlin. I have implemented my backend to provide me a signture and timestamp. This is what I have done to build the config:
var config = HashMap<String, Any> ()
config.put("cloud_name", "my_cloud_name");
//config.put("apiKey", my_api_key);
config.put("use_filename", true);
Now, I am unable to do the MediaManager.init using the signature. Can anyone please help? The Java code says to do the below, but I am unable to reproduce the same in Kotlin:
MediaManager.init(this, new SignatureProvider() {
#Override
public Signature provideSignature(Map options) {
// call server signature endpoint
}
}, null);

This is how you intialize MediaManager with a signature provider in Kotlin:
MediaManager.init(thiscontext!!, object: SignatureProvider {
override fun provideSignature(options: MutableMap<Any?, Any?>?): Signature {
return myBackendConnector.signRequest(options)
}
override fun getName(): String {
return "myCustomSignatureProvider"
}
}, config)
This will work assuming your backend already has the api key (it should), and that the return type from your connector is Signature. Otherwise you'll need to adapt your backend's result to Signature (populate the POJO with the result your server provided).

Get the timestamp and signature from your backend then add those as options.
val options = mapOf(
"timestamp" to // timestamp from backend,
"signature" to // signature from backend,
// other options from backend
)
MediaManager.get()
.upload(uri)
.options(options)
.callback(uploadCallback)
.dispatch()
So this means not creating a SignatureProvider in the MediaManager.init().
I came to using this because I could not get this to work with the SignatureProvider in the MediaManager.init().

Related

Ktor response streaming

I am trying to call a twitter endpoint that gives you a constant streams of json results back to the client
https://documenter.getpostman.com/view/9956214/T1LMiT5U#977c147d-0462-4553-adfa-d7a1fe59c3ec
I try to make a call to the endpoint like this
val url = "https://api.twitter.com/2/tweets/search/stream"
_streamChannel = _client.get<ByteReadChannel>(token, url) //Stops here
val byteBufferSize = 1024
val byteBuffer = ByteArray(byteBufferSize)
_streamChannel?.let {
while (_streamChannel!!.availableForRead > 0) {
_streamChannel!!.readAvailable(byteBuffer, 0, byteBufferSize)
val s = String(byteBuffer)
parseStreamResponseString(s).forEach {
emit(Response.Success(it))
}
}
}
my client.get code is this
suspend inline fun <reified T> get(authKey: String, url: String): T? {
val response = _client.get<HttpResponse>(url) {
header("Authorization", "Bearer $authKey")
}
when (response.status.value) {
in 300..399 -> throw RedirectResponseException(response)
in 400..499 -> throw ClientRequestException(response)
in 500..599 -> throw ServerResponseException(response)
}
if (response.status.value >= 600) {
throw ResponseException(response)
}
return response.receive<T>()
}
When I make the request it just sits there in what I am assuming is waiting for the full response to be returned before giving it to me.
Edit
I also tried using scoped streaming but it just sits at the line readAvailable I know there are messages coming through because when I run the request via cURL I am constantly getting data
_client.get<HttpStatement> {
header("Authorization", "Bearer $authKey")
url(urlString)
contentType(ContentType.Application.Json)
method = HttpMethod.Get
}.execute {
val streamChannel = it.receive<ByteReadChannel>()
val byteBufferSize = 1024
val byteBuffer = ByteArray(byteBufferSize)
streamChannel.readAvailable(byteBuffer, 0, byteBufferSize) // Stops here
val s = String(byteBuffer)
}
How do I process a constant stream of json data using Ktor?
As far as I am aware, the Ktor client does note expose access to the IO buffer of the request in the way that twitter's streaming API requires.
From the twitter documentation here:
Some HTTP client libraries only return the response body after the connection has been closed by the server. These clients will not work for accessing the Streaming API. You must use an HTTP client that will return response data incrementally. Most robust HTTP client libraries will provide this functionality. The Apache HttpClient will handle this use case, for example.
What you are doing is telling Ktor that the thing you are getting is a ByteReadChannel, and so, once the request closes (which will never happen with this twitter endpoint) the Ktor client would attempt to use whatever plugin (json for example) you were using to parse that data into a ByteReadChannel. It would also not be able to do that, because the data you are getting from twitter is not a ByteReadChannel, it is a new line seperated list of json objects.

Micronaut declarative client with base url per environment

I'd like to be able to use Micronaut's declarative client to hit an a different endpoint based on whether I'm in a local development environment vs a production environment.
I'm setting my client's base uri in application.dev.yml:
myserviceclient:
baseUri: http://localhost:1080/endpoint
Reading the docs from Micronaut, they have the developer jumping through quite a few hoops to get a dynamic value piped into the actual client. They're actually quite confusing. So I've created a configuration like this:
#ConfigurationProperties(PREFIX)
class MyServiceClientConfig {
companion object {
const val PREFIX = "myserviceclient"
const val BASE_URL = "http://localhost:1080/endpoint"
}
var baseUri: String? = null
fun toMap(): MutableMap<String, Any> {
val m = HashMap<String, Any>()
if (baseUri != null) {
m["baseUri"] = baseUri!!
}
return m
}
}
But as you can see, that's not actually reading any values from application.yml, it's simply setting a const value as a static on the class. I'd like that BASE_URL value to be dynamic based on which environment I'm in.
To use this class, I've created a declarative client like this:
#Client(MyServiceClientConfig.BASE_URL)
interface MyServiceClient {
#Post("/user/kfc")
#Produces("application/json")
fun sendUserKfc(transactionDto: TransactionDto)
}
The docs show an example where they're interpolating values from the config map that's built like this:
#Get("/api/\${bintray.apiversion}/repos/\${bintray.organization}/\${bintray.repository}/packages")
But how would I make this work in the #Client() annotation?
Nowhere in that example do they show how bintray is getting defined/injected/etc. This appears to be the same syntax that's used with the #Value() annotation. I've tried using that as well, but every value I try to use ends up being null.
This is very frustrating, but I'm sure I'm missing a key piece that will make this all work.
I'm setting my client's base uri in application.dev.yml
You probably want application-dev.yml.
But how would I make this work in the #Client() annotation?
You can put a config key in the #Client value using something like #Client("${myserviceclient.baseUri}").
If you want the url somewhere in your code use this:
#Value("${micronaut.http.services.occupancy.urls}")
private String occupancyUrl;

How to set body of HttpServletResponse using ktor client

I have spring boot controller
#PostMapping(path = ["/download"])
fun getFile(#RequestBody myObjectRq: myObjectRq, httpServletResponse: HttpServletResponse): CompletableFuture<HttpServletResponse> {
return GlobalScope.async {
val response = webService.getFile(myObjectRq)
response?.let {
httpServletResponse.setHeader("Content-Type", response.headers.get("Content-Type"))
httpServletResponse.setHeader("Content-Disposition", response.headers.get("Content-Disposition"))
httpServletResponse.writer.write(String(response.content.toByteArray()))
httpServletResponse.writer.flush()
httpServletResponse.status = response.status.value
}
httpServletResponse
}.asCompletableFuture()
}
in which I use service which in turn uses ktor client to send post request to external server which should respond sending csv file. csv file content depends on values I send in myObjectRq.
Service:
suspend fun getFile(myObjectRq: myObjectRq): HttpResponse {
val response = ktorClient.post<HttpResponse> {
accept(ContentType.Application.OctetStream)
url(externalWebServerUrl)
body = myObjectRq
contentType(ContentType.Application.Json)
}
log.info(String(response.content.toByteArray()))
response
}
Headers in response are properly set, also log.info(String(response.content.toByteArray())) in the method prints out the content of received file, but I can't set it as a body of HttpServletResponse. I keep getting org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation.
Also I get Inappropriate blocking method call for httpServletResponse.writer which kind of breaks async qualities of ktor client.
What do I do wrong? How should I solve it?
So, I think SpringBoot is confused with your return type. It is trying to find a way to serialize your return CompletableFuture<HttpServletResponse> into the body of the HTTP response but failing. I believe you can achieve the same result by changing your implementation as follows:
#PostMapping(path = ["/download"])
fun getFile(#RequestBody myObjectRq: myObjectRq, httpServletResponse: HttpServletResponse): CompletableFuture<Void> {
return GlobalScope.async {
val response = webService.getFile(myObjectRq)
response?.let {
httpServletResponse.setHeader("Content-Type", response.headers.get("Content-Type"))
httpServletResponse.setHeader("Content-Disposition", response.headers.get("Content-Disposition"))
httpServletResponse.writer.write(String(response.content.toByteArray()))
httpServletResponse.writer.flush()
httpServletResponse.status = response.status.value
}
null
}.asCompletableFuture()
}
I actually managed to solve this using CompletableFuture<ResponseEntity<ByteArray>> as return type and setting body of the response this way:
ResponseEntity.ok().body(response.content.toByteArray())
This also removed Inappropriate blocking method call warnings.

ServiceStack authentication with both [Authenticate] and [ValidateApiKey] attributes

I have some endpoints decorated with the [Authenticate] attribute. Now a third party client has to access the same endpoint by using a shared API key.
As the code would be exactly the same for the two cases, I would like to check first if the current request comes from an authenticated user and, if not, checks as fallback if a valid API key is provided.
Is there a way to use both [Authenticate] and [ValidateApiKey] attributes for the same endpoint?
Something like:
[Authenticate | ValidateApiKey]
public long Post(MyDto request)
{
// ....
}
Attributes can only be combined to add functionality, i.e. they can't be used as a fallback or a switch. To get the desired behavior your [ValidateApiKey] attribute should perform the validation fallback as part of its implementation, e.g:
public class ValidateApiKeyAttribute : RequestFilterAttribute
{
public override void Execute(IRequest req, IResponse res, object reqDto)
{
var session = req.GetSession();
if (session == null || !session.IsAuthenticated)
{
//If not a valid key, execute the `[Authenticate]` attribute
//to handle failed response
if (!CheckValidApiKey(req))
new AuthenticateAttribute().Execute(req,res,reqDto);
}
}
}
Note: Responses should be reference types (e.g. DTO's) or raw strings not value types.
public object Post(MyDto request)
{
// ....
}

worklight 6.2 native api adapter invocation

In JavaScript my Worklight Client can pass arbitrary objects to an adapter:
var payload = { able: 3488, baker: "wibble"};
var invocationData = {
adapter : 'xxx',
procedure : 'xxx',
parameters : [payload],
compressResponse : false
};
var options = {
onSuccess: onCallSuccess,
onFailure: onCallFailure
};
WL.Client.invokeProcedure(invocationData, options);
and the adapter can access the object
function xxx(p1) {
return {'answer': p1.able};
}
In the native APIs it seems that we are limited to primitive types:
public void setParameters(java.lang.Object[] parameters)
This method
sets the request parameters. The order of the object in the array will
be the order sending them to the adapter
Parameters: Object -
parameters An array of objects of primitive types ( String, Integer,
Float, Boolean, Double). The order of the objects in the array is the
order in which they are sent to the adapter.
Hence if my adapters are to be used by both JavaScript and Native clients they will need to accept any complex objects as JSON Strings. Unless there is an alternative I'm missing?
I don't see a better alternative than simply stringifying the object as you have suggested. Are you using objects other than JSON in your native side? If so what is the structure of the object?