Ktor custom feature swap url - ktor

Im trying to add a custom feature in Ktor. It's basically a url swapper (we have a scenario where domains might be changed during anytime & can't update the client everytime).
We get the swapper list available and need a CustomFeature in Ktor to swap the url based on list. However, the context.request or request.url - everything is val and Im not able to assign new url to the request.
In Retrofit, it used to work like this
if (currentUrl.contains(urlSwapper.oldUrl)) {
val newUrl = currentUrl.replace(urlSwapper.oldUrl, urlSwapper.newUrl)
val newHttpUrl = request.url.newBuilder(newUrl)!!.build()
// build a new request with the new url. replace it
request = request.newBuilder().url(newHttpUrl).build()
break
}
}
In Ktor feature, Im trying something like this
scope.requestPipeline.intercept(HttpRequestPipeline.Transform) {
val currentUrl =
context.url.protocol.name + "://" + context.url.host + context.url.encodedPath
for (urlSwapper in feature.urlSwappers) {
if (currentUrl.contains(urlSwapper.oldUrl)) {
val newUrl = currentUrl.replace(urlSwapper.oldUrl, urlSwapper.newUrl)
val newHttpUrl = Url(newUrl)
context.url(url = newHttpUrl)
break
}
}
proceedWith(subject)
}
}
Is this the right way to do this ?

Generally yes, this is the right way. I have a few recommendations:
Intercept the sendPipeline instead of the requestPipeline.
An example:
client.sendPipeline.intercept(HttpSendPipeline.State) {
context.url(url = newUrl)
}
Get rid of proceedWith(subject) call because it's redundant.
Try to use Url objects instead of strings. You can get the current URL without affecting context by cloning UrlBuilder and building Url from it: context.url.clone().build()

Related

Kotlin - get Firebase Image DownloadUrl returns "com.google.android.gms.tasks.zzu"

I am trying to upload images to Real time Firebase Database by creating two folders using Compressor library and need to display image like messenger with username but i am unable to display image due to url issue
var filePath = mStorageRef!!.child("chat_profile_images")
.child(userId + ".jpg")
//Create another directory for thumbimages ( smaller, compressed images)
var thumbFilePath = mStorageRef!!.child("chat_profile_images")
.child("thumbs")
.child(userId + ".jpg")
filePath.putFile(resultUri)
.addOnCompleteListener{
task: Task<UploadTask.TaskSnapshot> ->
if (task.isSuccessful) {
//Let's get the pic url
var donwloadUrl = task.result?.storage?.downloadUrl.toString()
Log.d(TAG, "Profilepic link: $donwloadUrl")
//Upload Task
var uploadTask: UploadTask = thumbFilePath
.putBytes(thumbByteArray)
uploadTask.addOnCompleteListener{
task: Task<UploadTask.TaskSnapshot> ->
var thumbUrl = task.getResult()?.storage?.downloadUrl.toString()
Log.d(TAG, "Profilepic link: $thumbUrl")
i tried to change downloadUrl
filepath.downloadUrl.toString
thumbFilePath.downloadUrl.toString
but both these values getting "com.google.android.gms.tasks.zzu"
i also tried to change
task.result.sessionurl.downloadUrl.toString
for this one i am getting downloadUrl but not a complete solution for my problem as still i cannot display image i need to get thumbUrl downloadUrl
You have the exact same and very common misunderstanding as in this question, except it's in java. You should follow the documentation here to understand get getDownloadUrl works. As you can see from the linked API documentation, it's not a property getter, it's actually a method that returns a Task<Uri> that tracks the asynchronous fetch of the URL you want, just like the upload task:
filePath.downloadUrl
.addOnSuccessListener { urlTask ->
// download URL is available here
val url = urlTask.result.toString()
}.addOnFailureListener { e ->
// Handle any errors
}
This will only work after the upload is fully complete.
Correct way of getting download link after uploading
here
Just putting out there, I also encounter the same problem,
but both these values getting "com.google.android.gms.tasks.zzu"
but it wasn't the same mistake from the OP
used addOnCompleteListener instead of addOnSuccesslistener
My Error code:
imageRef.downloadUrl.addOnCompleteListener { url ->
val imageURL = url.toString()
println("imageURL: $imageURL , url: $url")
addUserToDatabase(imageURL)
}

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;

Move Fastly config to CloudFlare Workers

How can the following code be re-written to work with the CF Workers feature?
# Start
if(req.url ~ "^/app" ) {
set req.url = regsub(req.url, "^/app/", "/");
set req.http.X-DR-SUBDIR = "app";
}
#end condition
Cloudflare Workers implements the Service Worker standard, so you will need to reimplement the VCL code snippet you posted in terms of a Service Worker.
Before I show you how to do that, consider what happens when a request for https://example.com/apple arrives at the proxy. I would expect the first regex for ^/app to match, but the second one for ^/app/ not to match -- i.e., the request would be passed through with no change to the URL, but with the addition of an X-DR-SUBDIR: app header.
I suspect that behavior is a bug, so I'll first implement a worker as if the first regex were ^/app/.
addEventListener("fetch", event => {
let request = event.request
// Unlike VCL's req.url, request.url is an absolute URL string,
// so we need to parse it to find the start of the path. We'll
// need it as a separate object in order to mutate it, as well.
let url = new URL(request.url)
if (url.pathname.startsWith("/app/")) {
// Rewrite the URL and set the X-DR-SUBDIR header.
url.pathname = url.pathname.slice("/app".length)
// Copying the request with `new Request()` serves two purposes:
// 1. It is the only way to actually change the request's URL.
// 2. It makes `request.headers` mutable. (The headers property
// on the original `event.request` is always immutable, meaning
// our call to `request.headers.set()` below would throw.)
request = new Request(url, request)
request.headers.set("X-DR-SUBDIR", "app")
}
event.respondWith(fetch(request))
})
To revisit the https://example.com/apple case, if we really wanted a Cloudflare Worker which pedantically reproduces the VCL snippet's behavior, we could change these lines (comments elided):
if (url.pathname.startsWith("/app/")) {
url.pathname = url.pathname.slice("/app".length)
// ...
}
to these:
if (url.pathname.startsWith("/app")) {
if (url.pathname.startsWith("/app/")) {
url.pathname = url.pathname.slice("/app".length)
}
// ...
}

uploading a file in a non-blocking manner without using gridFSBodyParser(gridFS)

The plugin play-reactivemongo offers an easy way to upload a file:
def upload = Action(gridFSBodyParser(gridFS)) { request =>
val futureFile: Future[ReadFile[BSONValue]] = request.body.files.head.ref
futureFile.map { file =>
// do something
Ok
}.recover { case e: Throwable => InternalServerError(e.getMessage) }
}
Unfortunately this solution doesn't suit me because:
I would like only my DAO layer to depend on reactive-mongo.
I need to save the file only if a user is authenticated (with SecureSocial) and use some user's properties as checks and metadata.
If no user is authenticated the request body shouldn't be parsed at all (see also this question).
It would be something along the lines
def upload = SecuredAction { request =>
val user = request.user
val enumerator = an enumrator from the body parsing ???
myDAO.saveFile(user, enumerator)
object myDAO {
def saveFile(user:User, enumerator:Enumerator[Array[Byte]]) = {
...
val fileToSave = DefaultFileToSave(...)
gridfs.save(enumerator, fileToSave)
...
}
}
Unfortunately it seems there is no way to get an enumerator from the parsing of the request body. The only way seems to provide the Action with a parser and an Iteratee that will be fed with the the body being parsed.
I couldn't figure out how to achieve it in a reactive way (without using a temporary file or storing the body in memory). Is it at all possible?
Actually, you might consider not using girdFS built-in parser at all:
val gfs = new GridFS(db)
// the controller method, Authenticated here is custom object extending ActionBuilder
def upload = Authenticated.async(parse.multipartFormData) { request =>
...
request.body.file("photo") match {
// handle error cases
...
case Some(photo) =>
val fileToSave = DefaultFileToSave(photo.filename, photo.contentType)
// here some more operations, basically you don't need the and need only photo.ref.file
val enumerator = Enumerator(Image(photo.ref.file).fitToWidth(120).write)
gfs.save(enumerator, fileToSave) map {
//handle responses and stuff
...
}
}
}
}

log4javascript - obtain history of messages programmatically?

I'm looking into using a javascript logging framework in my app.
I quite like the look of log4javascript (http://log4javascript.org/) but I have one requirement which I'm not sure that it satisfies.
I need to be able to ask the framework for all messages which have been logged.
Perhaps I could use an invisible InPageAppender (http://log4javascript.org/docs/manual.html#appenders) to log to a DOM element, then scrape out the messages from that DOM element - but that seems pretty heavy.
Perhaps I need to write my own "InMemoryAppender"?
There's an ArrayAppender used in log4javascript's unit tests that stores all log messages it receives in an array accessible via its logMessages property. Hopefully it should show up in the main distribution in the next version. Here's a standalone implementation:
var ArrayAppender = function(layout) {
if (layout) {
this.setLayout(layout);
}
this.logMessages = [];
};
ArrayAppender.prototype = new log4javascript.Appender();
ArrayAppender.prototype.layout = new log4javascript.NullLayout();
ArrayAppender.prototype.append = function(loggingEvent) {
var formattedMessage = this.getLayout().format(loggingEvent);
if (this.getLayout().ignoresThrowable()) {
formattedMessage += loggingEvent.getThrowableStrRep();
}
this.logMessages.push(formattedMessage);
};
ArrayAppender.prototype.toString = function() {
return "[ArrayAppender]";
};
Example use:
var log = log4javascript.getLogger("main");
var appender = new ArrayAppender();
log.addAppender(appender);
log.debug("A message");
alert(appender.logMessages);