An HTTP Framework

Related tags

App wasabi
Overview

Download TeamCity

Wasabi - An HTTP Framework

IMPORTANT

tl;dr We are merging with Ktor.

Ktor and Wasabi both have the same goals - define a simple, extensible HTTP framework for the JVM, built with Kotlin. Wasabi was created before Ktor and Ktor is influenced significantly by Wasabi. The recent commitment by the core team (@swishy and @hhariri) has unfortunately been impacted due to other commitments and we've decided that the best way to move forward is to join efforts with the #Ktor team to provide the best possible experience for our users. Given Wasabi and Ktor similarity, migration shouldn't be an issue and both the Wasabi team as well as the Ktor team are here to help (reach out to us on Kotlin Slack).

You can of course fork this project and continue it, or contribute your efforts to #Ktor!

Description

An HTTP Framework built with Kotlin for the JVM.

Wasabi combines the conciseness and expressiveness of Kotlin, the power of Netty and the simplicity of Express.js (and other Sinatra-inspired web frameworks) to provide an easy to use HTTP framework

What it is

An HTTP framework that allows you to easily create back-end services for a web application or any other type of application that might require an HTTP API.

What it is not

It is not an MVC framework. There is no View Engine or templating language. You can combine it with client-side frameworks such as AngularJS or Ember. If you want a fully-fledged MVC framework in Kotlin, take a look at Kara

It is not a REST framework. Primarily because there's no such thing, and while calling it REST might sell better, it would be false. However it does provide (and will provide) features that allow you to create resource-orientated systems that help comply with ReSTful constraints.

Current Status

In development. A lot of the stuff here is not ready for production. There are many experiments, quite a few hacks and things that are just wrong. So be warned.

Disclaimer

A lot of the API and design of this framework is based on experience developing applications with other HTTP frameworks and the needs that have arised. However, like any framework, until you don't have several usages of it, it's hard to know if you got it right. So while I'm hoping that there won't be major API changes, some things might change over time.

Getting Started

The Hello World of Wasabi

var server = AppServer()

server.get("/", { response.send("Hello World!") })

server.start()

Starting a new application

The easiest way to use Wasabi is with Gradle.

  1. Download Gradle.

  2. Create the build.gradle file and import it into your IDE.

To create this file you can either:

  • Have Gradle generate the file for you (easiest) running the build.gradle file under tools (Recommended). To use this option download the file in tools/build.gradle and type

      gradle
    

    for more info

  • Create it manually. See the sample for the structure. Make sure you fill in the TODOs

Including as a dependency (Gradle)

Follow the instructions on BinTray for adding a dependency in your build system.

You will need to add the repository to the repositories section also as per below.

repositories {
    mavenCentral()
    maven{
        url 'https://dl.bintray.com/wasabifx/wasabifx/'
    }
}

Important: Versioning

Kotlin is still in development and so is Wasabi. The current master trunk of Wasabi uses the latest released version of Kotlin.

The AppServer

Each Wasabi application is composed of a single AppServer on which you define route handlers. A route handler can respond to any of the HTTP verbs: GET, POST, PUT, DELETE, OPTIONS, HEAD. A normal application consists of a section where you define a series of parameters for the application, followed by your handlers (i.e. your routing table).

var server = AppServer()

server.get("/customers", { .... } )
server.post("/customer", { .... } )

server.start()

Route Handlers

In Wasabi, every request is processed by one or more route handlers. In the previous example, we are responding to a GET to "/" with the text "Hello World!". You can chain route handlers. For instance, if you want to log information about a request (this is actually built-in so no need to do it manually), you could do

  server.get("/",
    {
      val log = Log()

      log.info("URI requested is ${request.uri}")
      next()
    },
    {
      response.send("Hello World!")
    }
  )

or as follows enforces JSON serialisation regardless of the request accept header.

  server.get("/",
    {
      val log = Log()

      log.info("URI requested is ${request.uri}")
      next()
    },
    {
      response.send("Hello World!", "application/json")
    }
  )

By calling next() on each handler, the processing will continue.

All verbs on AppServer have the following signature

fun get(path: String, vararg handlers: RouteHandler.() -> Unit) {
  addRoute(HttpMethod.GET, path, *handlers)
}

where you can pass one or multiple route handlers. Each one of these is an extension method to the class RouteHandler. This class has various properties, amongst which are request and response. That is how you can access these properties from inside each of the functions without an explicit declaration.

Route Parameters

Wasabi supports route parameters. Define as many parameters as you like using : followed by the name of the parameter. Access it via request.routeParams["name"]

server.get("/customer/:id", { val customerId = request.routeParams["id"] } )

Query Parameters

Access query parameters using queryParams property of the request.

http://localhost:3000/customer?name=Joe

server.get("/customer", { val customerName = request.queryParams["name"] } )

Form Parameters

Access form parameters using bodyParams property of the request.

server.post("/customer", { val customerNameFromForm = request.bodyParams["name"] } )

Organization of Route Handlers and Application layout

How you layout the code for your application or group your routes depends largely on your own choice. One thing I've always been against is forcing people to group routes per class for instance. Having said that, there are some bounds you need to stay in.

Option 1

Defining logic for each route handler inline:

val appServer = AppServer()

appServer.get("/customer", { response.send(customers) })

For very simple operations this might be ok however, it will soon become unmaintainable.

Option 2

Define route handlers as functions and reference them:

val appServer = AppServer()

appServer.get("/customer", getCustomers)

This means that your definition of route handlers pretty much becomes a routing table, which is what it should be.

This is the preferred option. You can then group functions however way you want:

Grouping by file

Group similar routes in its own file. As Kotlin allows top level functions, you do not need to have a class to group functions. As such, you could have a file name CustomerRouteHandlers.kt for instance with:

val getCustomers = routeHandler {

  response.send(customers)

}

val getCustomerById = routeHandler {
  ...
}

routeHandler is a syntatic sugar to define the type of the route handler. You could also have writte that as:

val getCustomers : RouteHandler.() -> Unit = {

  response.send(customers)

}

val getCustomerById  : RouteHandler.() -> Unit = {
  ...
}
Grouping by class

If for some reason you want to group by class, you can do so. Best way is to use a companion object

class CustomerRoutes {

    companion object {

        val createCustomer = routeHandler {

        }

    }

}

Note Kotlin allows you to refer to a function by name using :: syntax. In principle you could also do:

appServer.get("/customer", ::getCustomer)

and not require an explicit variable declaration for the function. However, this currently does not work with extension functions but hopefully will in the future.

Interceptors

In addition to handlers, Wasabi also has interceptors. These allow you to intercept a request and decide whether you want it to continue or not (returning false would stop processing). Since you have access to both the request and response, you can do whatever you need. Think of interceptors as a way to add functionality to every request, or a those matching a certain route pattern. Some frameworks have popularized the term middleware to refer to something that intercepts a request/response. I do not agree with such a broad and somewhat ambiguous term. I like to name things as close to what they actually do. An interceptor implements the following interface

interface Interceptor {
  fun intercept(request: Request, response: Response): Boolean
}

You return true if you want the process to continue or false if you want to interrupt the request.

To add an interceptor to the application, you use

server.interceptor(MyInterceptor(), path, position)

where path can be a specific route or *** to match all routes. Position indicates when the intercept occurs. Possible positions are

 enum class InterceptOn {
     PreRequest
     PreExecution
     PostExecution
     PostRequest
     Error
 }

Out of the box, the following interceptors are available

  • BasicAuthenticationInterceptor: Basic authentication
  • ContentNegotiationInterceptor: Automatic Content negotation
  • FavIconInterceptor: Support for favicon
  • StaticFileInterceptor: Support for serving static files
  • LoggingInterceptor: Logging
  • FileBasedErrorInterceptor: Customize error messages based on convention (e.g. 404.html)
  • SessionManagementInterceptor: Session Management support

Most interceptor add extension methods to AppServer to make them easier (and more descriptive) to use. For instance the ContentNegotiationInterceptor and StaticFileInterceptor would be used as so

val appServer = AppServer()

server.negotiateContent()
server.serveStaticFilesFromFolder("/public")

Content Negotiation

Wasabi ships with content negotiation out of the box, via a couple of interceptors. In particular:

  • ContentNegotiationParserInterceptor (still looking for a better name) Allows you to indicate to Wasabi to not only take into account Accept Headers but also Query Fields and Extensions on documents

  • ContentNegotiationInterceptor Does the actual content negotiation, finding the appropriate serializer.

How it works

Content Negotiation Parsing

Sometimes you want to not only do Content Negotiation using the Accept headers, but also using a query field (for instance format=json) or extensions to documents (e.g. /customer.json).

The ContentNegotiationParser allows you to do this. Easiest way to use it is via the extension function:

server.parseContentNegotiationHeaders() {
    onQueryParameter("format")
    onExtension()
    onAcceptHeader()
}

Based on the order in which you pass in onAcceptHeader, onExtension and onQueryParameter defines the priority. Above for instance first the Query Parameter is taking into account, then extension and lastly the Accept Header

QueryParameter defaults to the query parameter format but you can pass in a different one. Both onExtension and onQueryParameter also take a list of mappings, which an array of extension to media type. By default json maps to application/json and xml to application/xml

Automatic Content Negotiation

By default Content Negotiation is enabled. You can disable it via the AppConfiguration. You can then just send and object you want and Wasabi will automatically serialize it and send it back to the client.

server.get("/customer/:id", {
  val customer = getCustomerById(request.params["id"])
  response.send(customer)
}

Wasabi will automatically serialize that into Json, Xml or any other media type supported (see Serializers below)

Manual

If you need to manually override Content Negotiation, you can do so using the negotiate function on response

   server.get("/customer/:id", {


        val customer = getCustomerById(request.params["id"])

        response.negotiate (
            "text/html" with { send("Joe Smith") },
            "application/json" with { send(customer) }
        }


}

negotiate signature is:

fun negotiate(vararg negotiations: Pair<String, Response.() -> Unit>)

You can pass in an unlimited number of media type, functions. Also, as the function is an extension function to Response, you have access to the response functions directly. That is why we can just write send as opposed to response.send.

Note that even if you use manual content negotiation, Wasabi will still try and serialize the object for you based on the media type.

Serializers

Both manual as well as automatic Content Negotiation use serializers. Wasabi ships with Json and XML (TODO) serializers. These are defined as a property of the AppServer.

Each Serializer can take as parameters a variable number of strings which define regular expressions for the media types it can handle. JsonSerializer for instance takes:

class JsonSerializer(): Serializer("application/json", "application/vnd\\.\\w*\\+json")

CORS Support

Wasabi provides CORS support via an Interceptor. You can enable CORS support in multiple ways:

  • Via AppConfiguration.enableCORSGlobally - which enables CORS for all routes, all verbs, all origins
  • Programatically using server.enableCORSGlobally - same as above
  • Programatically using server.enablesCORS([...COREntry...]) - providing an array of CORSEntry's

Each CORSEntry consists of:

class CORSEntry(val path: String = "*",
                       val origins: String = "*",
                       val methods: String = "GET, POST, PUT, DELETE",
                       val headers: String = "Origin, X-Requested-With, Content-Type, Accept",
                       val credentials: String = "",
                       val preflightMaxAge: String = "") {

}

with the corresponding defaults. Obviously you should only have one default.

Auto Options Support

Wasabi can automatically respond to OPTIONS for a specific path. You can enable this in multiple ways:

  • Via AppConfiguration.enableAutoOptions
  • Programatically using server.enableAutoOptions - same as above

Exception Handlers

Wasabi allows registration of custom handlers by exception type also. You can register them as follows:

val appServer = AppServer()
appServer.exception(MyKewlException::class, {
    reponse.setStatus(418, "My brew is not as strong as yours!")
    response.send("Out of beans: ${exception.message}")
})

like other Wasabi handlers the registered exception handler gets passed the request and response as well as the exception thrown.

WebSocket Support

Wasabi has initial WebSocket support which automatically handles websocket handshake and socket upgrade. Wasabi allows the registration of multiple 'channels' per appserver and you can declare a handler per channel. As our websocket support matures we intend to allow the registration of interceptors and serializers in the manner our HTTP / HTTP/2 pipeline does currently.

Example showing direct reply to the current client.

    appServer.channel("/foo", {
        if (frame is TextWebSocketFrame) {
            val textFrame = frame as TextWebSocketFrame
            respond(ctx!!.channel(), TextWebSocketFrame(textFrame.text().toUpperCase()))
        }
    })

Example showing message broadcast from channel.

    appServer.channel("/foo", {
        if (frame is TextWebSocketFrame) {
            val textFrame = frame as TextWebSocketFrame
            broadcast(TextWebSocketFrame(textFrame.text().toUpperCase()))
        }
    })

Example showing triggering broadcast from webservice.

    appServer.post("/message", {
        val message = request.bodyParams["message"]
        broadcast("/foo", TextWebSocketFrame(message))
        response.send()
    })

Community

We're mostly hanging out on the #wasabi Channel on Kotlin's Slack. Join us there for questions and discussions.

Contributions

There's a lot of work still pending and any help would be appreciated. Pull Requests welcome!

We have the project building on TeamCity. Click on the Icon to go to the build

License

Licensed under Apache 2 OSS License

Thank you's

Big thank you to JProfiler for the OSS license to help improve Wasabi performance

Comments
  • Adding pre/post interceptor hooks

    Adding pre/post interceptor hooks

    Patch adds pre post interceptor hooks, they are both global and called at start / end of the pipeline, post has explicit exception handling but wasnt sure if we need to clear the response body prior to writing to the stream.

    opened by swishy 12
  • Fixed issue with AutoOptionsInterceptor

    Fixed issue with AutoOptionsInterceptor

    Two issues solved here

    1. If I have defined routes with route parameters (let's say, GET /todos/:id/), AutoOptionsInterceptor does not match it, because it compares strings of current request's path with route's path. Result gets this: '/todos/:id/' == '/todos/16', so it always will be false
    2. Added additional response headers for browser to have full CORS support for other methods , such as PUT, DELETE, etc.
    opened by akoncius 9
  • Virtual Host with built-in Wasabi web server

    Virtual Host with built-in Wasabi web server

    I need say.. This project is amazing!

    Well, when I compile my project with this:

    package kotlinApi
    
    import org.wasabi.app.AppServer
    
    fun main(args: Array<String>){
        var server = AppServer()
    
        server.get("/hello", { response.send("Hello World!") })
    
        server.start()
    }
    

    I receive on my console:

    [main] INFO org.wasabi.app.AppServer - Server starting on port 3000
    [nioEventLoopGroup-3-1] INFO org.wasabi.interceptors.LoggingInterceptor - [GET] - /
    [nioEventLoopGroup-3-2] INFO org.wasabi.interceptors.LoggingInterceptor - [GET] - /hello
    

    It seems to me that the Wasabi has a built-in web server.

    The default host is http://localhost:3000

    How do I set up a virtual host with this built-in web server?

    opened by bkawakami 9
  • Can't get it to install on IntelliJ IDEA CE

    Can't get it to install on IntelliJ IDEA CE

    Hi there,

    I'm currently trying to install Wasabi to use as my static file server. It doesn't get to install completely I think. What I did was:

    • Made a Kotlin project on IDEA with gradle.

    • Created a folder src/main/kotlin in my IDE. See screenshot.

      • screenshot from 2016-08-22 11-10-48
    • Included wasabi in my gradle file, which looks like this:

      group 'Viewer'
      version '1.0-SNAPSHOT'
      
      buildscript {
      ext.kotlin_version = '1.0.3'
      
      repositories {
          mavenCentral()
      }
      dependencies {
          classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
      }
      }
      
      apply plugin: 'java'
      apply plugin: 'kotlin'
      
      sourceCompatibility = 1.5
      
      repositories {
      mavenCentral()
      maven { url 'http://repository.jetbrains.com/all' }
      }
      
      dependencies {
      compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
      compile 'org.wasabifx:wasabi:0.1-SNAPSHOT'
      
      testCompile group: 'junit', name: 'junit', version: '4.11'
      }
      
    • Then, added the server code from the example:

    fun runServer() {
        val appServer = AppServer()
        server.negotiateContent()
        server.serveStaticFilesFromFolder("/public")
    }
    

    But it cant find AppServer and it also says this about my repositories: screenshot from 2016-08-22 11-13-19

    When I do what it says, and update, I get this: screenshot from 2016-08-22 11-13-31

    This was a fresh project, if anyone has a suggestion then that's great! Thanks!

    opened by peterwilli 8
  • Calling send(content,

    Calling send(content, "contentType") does not set Content-Type header to "contentType"

    When you call response.send("some content", "some content type"), I would expect this to result in a Content-Type header with value some content type. However, it has no effect -- the content type is whatever was manually set, or text/plain if never set.

    I believe the problem is that response.send only updates the negotiatedMediaType property and never propagates that value to the contentType property -- or, if it does, it's doing it too late after response.setHeaders has already been called.

    opened by ean5533 6
  • Exception handling by type

    Exception handling by type

    Please, provide solution to handle exceptions by type with wasabi in order to map exceptions to http status and content.

    There are some convenient ways that other provide such as: Sinatra ruby: http://www.sinatrarb.com/intro.html error MyCustomError do 'So what happened was...' + env['sinatra.error'].message end

    Spark java: http://sparkjava.com/documentation.html#exception-mapping exception(YourCustomException.class, (exception, request, response) -> { // Handle the exception here });

    Spring java: https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc @ResponseStatus annotation above exception class

    I found out a way to register an interceptor on error, unfortunately, there is no way to handle particular error types. I believe, it won't be difficult to implement it in wasabi: HttpRequestHandler:93: } catch (e: Exception) { //process an exception here }

    opened by Tradunsky 5
  • vararg in Route class not allowed

    vararg in Route class not allowed

    Compilation fails with this message: src/main/kotlin/org/wasabi/routing/Route.kt: (9, 104): Primary constructor vararg parameters are forbidden for data classes

    Something to do with compiler change that the library has not addressed?

    opened by aneophyte 5
  • NoSuchMethod on request

    NoSuchMethod on request

    using the new commit i get

    java.lang.NoSuchMethodError: kotlin.StringsKt.lastIndexOf$default(Ljava/lang/CharSequence;Ljava/lang/String;IZI)I
        at org.wasabi.http.Request.<init>(Request.kt:15)
        at org.wasabi.http.HttpRequestHandler.handleRequest(HttpRequestHandler.kt:41)
        at org.wasabi.http.NettyRequestHandler.channelRead0(NettyRequestHandler.kt:76)
        at io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:105)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:308)
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:294)
        at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:103)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:308)
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:294)
        at io.netty.channel.ChannelInboundHandlerAdapter.channelRead(ChannelInboundHandlerAdapter.java:86)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:308)
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:294)
        at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:244)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:308)
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:294)
        at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:846)
        at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:131)
        at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:511)
        at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:468)
        at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:382)
        at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:354)
        at io.netty.util.concurrent.SingleThreadEventExecutor$2.run(SingleThreadEventExecutor.java:112)
        at io.netty.util.concurrent.DefaultThreadFactory$DefaultRunnableDecorator.run(DefaultThreadFactory.java:137)
        at java.lang.Thread.run(Thread.java:745)
    
    opened by ghost 4
  • Add support for specifying hostname alongside port via AppConfiguration

    Add support for specifying hostname alongside port via AppConfiguration

    I am building a web server that supports a local browser-based app. I do not want that web server to be accessible to other devices on the network (e.g. other people at a coffeeshop). So, as a start, I’d like to specify the hostname that the wasabi HTTPServer binds to be 127.0.0.1 instead of the default wildcard (0.0.0.0).

    I am leaving the default hostname as null, which uses the previous behavior. I believe this is consistent with NodeJS’s defaults, so I don’t have motivation to change the defaults (athough, it would be more conservative to not assume the user wants their in-development web server to be accessible to other devices on the network).

    I did however change the AppServer used during tests to use 127.0.0.1 instead of 0.0.0.0. Since tests are run locally, this change didn’t require any other changes. Hypothetically, this would prevent tests randomly being influenced by some unexpected traffic on your network (again e.g., someone port scanning at a coffee shop).

    Unfortunately, I could not think of a way to test the negative - that servers are inaccessible from outside of localhost. I did test this manually in my real-world app, though.

    opened by jshmrsn 3
  • Content Negotiation: Font

    Content Negotiation: Font

    When using the StaticFileInterceptor, .woff, .woff2 and .ttf files (from Font-Awesome) have their content-type set to text/plain instead of application/font-woff , application/font-woff2 and application/octet-stream respectively.

    [GET] - /components/font-awesome/fonts/fontawesome-webfont.woff2?v=4.6.3 [Etag=, Location=, Content-Type=text/plain, Connection=close, Cache-Control=max-age=0]

    [GET] - /components/font-awesome/fonts/fontawesome-webfont.woff?v=4.6.3 [Etag=, Location=, Content-Type=text/plain, Connection=close, Cache-Control=max-age=0]

    [GET] - /components/font-awesome/fonts/fontawesome-webfont.ttf?v=4.6.3 [Etag=, Location=, Content-Type=text/plain, Connection=close, Cache-Control=max-age=0]

    opened by AndreasVolkmann 3
  • Path-based interceptors mangle path parameters under load

    Path-based interceptors mangle path parameters under load

    Under moderate load, it appears that interceptors that are path-scoped cause path parameters to end up with a value of the parameter name, rather than the parameter value. It doesn't happen for every request, but it happens quite often, which suggests some sort of race condition in the path-parsing code.

    This bug is seen in version 0.2.0 (compile(group: 'org.wasabifx', name: 'wasabi', version: '0.2.0'))

    Example code which quickly reproduces the issue:

    Server:

    fun main(args: Array<String>) {
        val appServer = AppServer(AppConfiguration(port = 8080, enableLogging = false)).apply {
            intercept(object : Interceptor() {
                override fun intercept(request: Request, response: Response): Boolean = true
            }, "/api/:param/things", InterceptOn.PreExecution)
            get("/api/:param1/things", {
                val paramValue = request.routeParams["param1"]
                if (paramValue == "abc123") {
                    response.send("ok!")
                } else {
                    println("request param got messed up. should be 'abc123' but was $paramValue")
                    response.statusCode = 500
                    response.send("sad")
                }
            })
    
        }
    
        appServer.start()
    }
    

    Test Client:

    
    import okhttp3.OkHttpClient
    import okhttp3.Request
    import java.util.concurrent.LinkedBlockingQueue
    import java.util.concurrent.ThreadPoolExecutor
    import java.util.concurrent.TimeUnit
    import java.util.concurrent.atomic.AtomicLong
    
    fun main(args: Array<String>) {
        val okHttpClient = OkHttpClient()
        val count = AtomicLong()
    
        val executorService = ThreadPoolExecutor(4, 4, 0L, TimeUnit.MILLISECONDS, LinkedBlockingQueue<Runnable>(4), ThreadPoolExecutor.CallerRunsPolicy())
        while (true) {
            executorService.execute { request(count, okHttpClient) }
        }
    }
    
    private fun request(count: AtomicLong, okHttpClient: OkHttpClient) {
        val currentCount = count.incrementAndGet()
        if (currentCount % 1000 == 0L) {
            print(".")
        }
        val call = okHttpClient.newCall(Request.Builder().url("http://localhost:8080/api/abc123/things").build())
        val response = call.execute()
        if (!response.isSuccessful) {
            println("response = ${response.code()}")
        }
        response.close()
    }
    
    bug 
    opened by jkwatson 3
  • Rework of firing interceptors.

    Rework of firing interceptors.

    Pre/Post execution interceptors have already been stripped out, the pre and post interceptors are now only fired at the start/end of the pipeline and per handler, the current implementation needs to be reviewed to make sure we are only firing once per route and once at start / end of wasabi logic.

    enhancement 
    opened by swishy 0
  • GSON support.

    GSON support.

    Implement GSON JSON de-serialiser / serialiser support within wasabi. The current 'Json' deserialiser/serialiser has been renamed to reflect the Jackson parser under the hood.

    enhancement 
    opened by swishy 0
  • Provide access to client disconnection

    Provide access to client disconnection

    Currently wasabi provides no mechanism for developers to be notified when a websocket client disconnects, this needs to be resolved so they can release contexts, this is also an issue internally as wasabi doesn't clear out clients associated with handlers

    opened by swishy 1
Releases(v0.3)
Owner
null
Provides feign request interceptors to sign http requests using AWS Signature V4.

feign-aws-sigv4 Provides feign request interceptors to sign http requests using AWS Signature V4. Usage with SDK V1 Include the following dependency i

Tommy Schmidt 2 Nov 11, 2022
Extensible Android mobile voice framework: wakeword, ASR, NLU, and TTS. Easily add voice to any Android app!

Spokestack is an all-in-one solution for mobile voice interfaces on Android. It provides every piece of the speech processing puzzle, including voice

Spokestack 57 Nov 20, 2022
android-trinity is tiny proactive framework with much of the scaffolding code required to start a new Android Application.

android-trinity This is tiny framework with much of the scaffolding code (with some nice utilities and prepared source code) required to start a new A

Fernando Cejas 49 Nov 24, 2022
An android Quiz App in kotlin framework and uses Appwrite as backend

Quiz App Intro An android Quiz App in kotlin framework and uses Appwrite as backend How to clone and run the project: Cloning : git clone https://gith

null 2 Oct 22, 2022
The News App has been carried out within the framework of the MVVM architecture, information about news is obtained by consulting an API, it is built usisng Jetpack Copose, Coroutines, Dependency Injection with Hilt and Retrofit

Journalist The News App consists of an application that displays the latest news from EEUU from an API that provides official and updated information.

null 0 Nov 3, 2021
conceptual Android audio plugin framework

AAP: Android Audio Plugin Framework disclaimer: the README is either up to date, partially obsoleted, or sometimes (but not very often) ahead of imple

Atsushi Eno 42 Nov 12, 2022
A framework for building native applications using React

React Native Learn once, write anywhere: Build mobile apps with React. Getting Started · Learn the Basics · Showcase · Contribute · Community · Suppor

Meta 106.9k Jan 8, 2023
Slack app example for Heroku deployment, written in Kotlin, using Bolt framework.

slack-kotlin-heroku-example Slack app example for Heroku deployment, written in Kotlin, using Bolt framework. You need to configure your Slack app to

null 0 Dec 25, 2021
Hobby-keeping - Platform to record books that you read and games you played! Made with Kotlin and Spring Framework

Hobby Keeping API to record books that you read and games you played! Made with

William Barom Mingardi 1 Jan 29, 2022
Photon Framework provides cool way to Discord Slash Commands 👩‍💻 🚧

Photon Framework provides cool way to Discord Slash Commands ??‍?? ??

Codename Photon 16 Dec 20, 2022
weiV(pronounced the same as wave), a new declarative UI development framework based on the Android View system.

weiV(pronounced the same as wave) 简体中文 if ("weiV" == "View".reversed()) { Log.d( "weiV", "It means Inversion of Control, you shoul

fangbing chen 69 Nov 22, 2022
Simple application with some famous graph algorithm implemented by Jetpack Compose framework

GraphAlgorithm This Application was implemented by Jetpack Compose framework. The dagger-hilt library was used for dependency injection and Room libra

Amirreza lotfi 8 Aug 17, 2022
The Leading Security Assessment Framework for Android.

drozer ---------------------------------------------------------------- NOTE We would like to formally announce that F-Secure has stopped further deve

WithSecure Labs 3k Jan 8, 2023
Android File Fuzzing Framework

Droid-FF : install python dependencies (setup.sh ) and you are good to go. GDB Server for android : get it from @ wget https://people.mozilla.org/~nch

xyz 81 Nov 17, 2022
A lightweight tracking framework based on the tracking idea of Buzzvideo.(基于西瓜视频的责任链埋点思路实现的轻量级埋点框架)

Tracker English | 中文 Tracker is a lightweight tracking framework based on the tracking idea of Buzzvideo. Tracking idea Why use chain of responsibilit

DylanCai 76 Dec 22, 2022
Android Easy Http - Simplest android http request library.

Android Easy Http Library 繁體中文文檔 About Android Easy Http Library Made on OkHttp. Easy to do http request, just make request and listen for the respons

null 13 Sep 30, 2022
Asynchronous Http and WebSocket Client library for Java

Async Http Client Follow @AsyncHttpClient on Twitter. The AsyncHttpClient (AHC) library allows Java applications to easily execute HTTP requests and a

AsyncHttpClient 6k Jan 8, 2023
Square’s meticulous HTTP client for the JVM, Android, and GraalVM.

OkHttp See the project website for documentation and APIs. HTTP is the way modern applications network. It’s how we exchange data & media. Doing HTTP

Square 43.4k Jan 5, 2023
A type-safe HTTP client for Android and the JVM

Retrofit A type-safe HTTP client for Android and Java. For more information please see the website. Download Download the latest JAR or grab from Mave

Square 41k Jan 5, 2023
Permanently moved to http://github.com/Bilibili/ijkplayer

ijkplayer Platform Build Status Android iOS Video player based on ffplay Download Android: Gradle # required allprojects { repositories {

bbcallen 35 Oct 24, 2022