{"id":13428853,"url":"https://github.com/wasabifx/wasabi","last_synced_at":"2025-03-16T02:30:43.667Z","repository":{"id":7648814,"uuid":"9009498","full_name":"wasabifx/wasabi","owner":"wasabifx","description":"An HTTP Framework","archived":false,"fork":false,"pushed_at":"2019-11-08T08:43:54.000Z","size":17659,"stargazers_count":501,"open_issues_count":20,"forks_count":56,"subscribers_count":36,"default_branch":"master","last_synced_at":"2024-10-29T15:01:29.431Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wasabifx.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2013-03-25T15:37:06.000Z","updated_at":"2024-07-19T15:01:52.000Z","dependencies_parsed_at":"2022-08-23T16:30:51.034Z","dependency_job_id":null,"html_url":"https://github.com/wasabifx/wasabi","commit_stats":null,"previous_names":["hhariri/wasabi"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wasabifx%2Fwasabi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wasabifx%2Fwasabi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wasabifx%2Fwasabi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wasabifx%2Fwasabi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wasabifx","download_url":"https://codeload.github.com/wasabifx/wasabi/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243815607,"owners_count":20352196,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T01:01:06.876Z","updated_at":"2025-03-16T02:30:43.242Z","avatar_url":"https://github.com/wasabifx.png","language":"Kotlin","funding_links":[],"categories":["Libraries","开源库和框架","Kotlin","开发框架","\u003ca name=\"Kotlin\"\u003e\u003c/a\u003eKotlin"],"sub_categories":["网络"],"readme":"[![Download](https://api.bintray.com/packages/wasabifx/wasabifx/wasabi/images/download.svg) ](https://bintray.com/wasabifx/wasabifx/wasabi/_latestVersion)\n[![TeamCity](http://teamcity.jetbrains.com/app/rest/builds/buildType:(id:Wasabi_FullBuild)/statusIcon)](http://teamcity.jetbrains.com/viewType.html?buildTypeId=Wasabi_FullBuild)\n \nWasabi - An HTTP Framework\n========================\n\n### IMPORTANT ###\n\ntl;dr We are merging with [Ktor](https://github.com/kotlin/ktor). \n\nKtor 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](https://kotlinlang.slack.com)). \n\nYou can of course fork this project and continue it, or contribute your efforts to #Ktor!\n\n#### Description ####\nAn HTTP Framework built with [Kotlin](http://kotlin.jetbrains.org) for the JVM.\n\nWasabi combines the conciseness and expressiveness of Kotlin, the power of Netty and the simplicity of Express.js (and other Sinatra-inspired web frameworks)\nto provide an easy to use HTTP framework\n\n#### What it is ####\nAn HTTP framework that allows you to easily create back-end services for a web application or any other type of application that\nmight require an HTTP API.\n\n#### What it is not ####\n**It is not an MVC framework**. There is no View Engine or templating language. You can combine it with client-side frameworks such\nas AngularJS or Ember. If you want a fully-fledged MVC framework in Kotlin, take a look at [Kara](http://www.karaframework.com)\n\n**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)\nfeatures that allow you to create resource-orientated systems that help comply with ReSTful constraints.\n\n#### Current Status ####\nIn 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.\n\n#### Disclaimer ####\nA lot of the API and design of this framework is based on experience developing applications with other HTTP frameworks and the needs\nthat 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\nI'm hoping that there won't be major API changes, some things might change over time.\n\n\nGetting Started\n---------------\n\n#### The Hello World of Wasabi ####\n```kotlin\nvar server = AppServer()\n\nserver.get(\"/\", { response.send(\"Hello World!\") })\n\nserver.start()\n```\n\n#### Starting a new application ####\nThe easiest way to use Wasabi is with Gradle.\n\n1. Download Gradle.\n\n2. Create the build.gradle file and import it into your IDE.\n\n  To create this file you can either:\n\n  * Have Gradle generate the file for you (easiest) running the build.gradle file under tools (Recommended). To use this option\n     download the file in [tools/build.gradle](tools/build.gradle) and type\n\n     ```\n       gradle\n     ```\n     for more info\n\n  * Create it manually. See the [sample for the structure](tools/sample.gradle). Make sure you fill in the TODOs\n\n#### Including as a dependency (Gradle) ####\n\nFollow the instructions on [BinTray](https://bintray.com/wasabifx/wasabifx/wasabi) for adding a dependency in your build system.\n\nYou will need to add the repository to the repositories section also as per below.\n\n```groovy\nrepositories {\n    mavenCentral()\n    maven{\n        url 'https://dl.bintray.com/wasabifx/wasabifx/'\n    }\n}\n```\n\n### Important: Versioning\n\nKotlin is still in development and so is Wasabi. The current master trunk of Wasabi uses the latest released version of Kotlin. \n\n### The AppServer ###\nEach 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.\nA 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).\n\n```kotlin\nvar server = AppServer()\n\nserver.get(\"/customers\", { .... } )\nserver.post(\"/customer\", { .... } )\n\nserver.start()\n```\n\n### Route Handlers ###\nIn 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!\".\nYou 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\n\n```kotlin\n  server.get(\"/\",\n    {\n      val log = Log()\n\n      log.info(\"URI requested is ${request.uri}\")\n      next()\n    },\n    {\n      response.send(\"Hello World!\")\n    }\n  )\n```\n\nor as follows enforces JSON serialisation regardless of the request accept header.\n\n```kotlin\n  server.get(\"/\",\n    {\n      val log = Log()\n\n      log.info(\"URI requested is ${request.uri}\")\n      next()\n    },\n    {\n      response.send(\"Hello World!\", \"application/json\")\n    }\n  )\n```\n\nBy calling *next()* on each handler, the processing will continue.\n\nAll verbs on *AppServer* have the following signature\n\n```kotlin\nfun get(path: String, vararg handlers: RouteHandler.() -\u003e Unit) {\n  addRoute(HttpMethod.GET, path, *handlers)\n}\n```\n\nwhere 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\n*request* and *response*. That is how you can access these properties from inside each of the functions without an explicit declaration.\n\n\n\n#### Route Parameters ####\nWasabi supports route parameters. Define as many parameters as you like using : followed by the name of the parameter. Access it via request.routeParams[\"name\"]\n\n```kotlin\nserver.get(\"/customer/:id\", { val customerId = request.routeParams[\"id\"] } )\n```\n\n#### Query Parameters ####\nAccess query parameters using queryParams property of the request.\n\n  http://localhost:3000/customer?name=Joe\n\n```kotlin\nserver.get(\"/customer\", { val customerName = request.queryParams[\"name\"] } )\n```\n\n#### Form Parameters ####\nAccess form parameters using bodyParams property of the request.\n```kotlin\nserver.post(\"/customer\", { val customerNameFromForm = request.bodyParams[\"name\"] } )\n```\n\n### Organization of Route Handlers and Application layout ###\n\nHow you layout the code for your application or group your routes depends largely on your own choice. One thing I've always been\nagainst is forcing people to group routes per class for instance. Having said that, there are some bounds you need to stay in.\n\n##### Option 1\nDefining logic for each route handler inline:\n\n```kotlin\nval appServer = AppServer()\n\nappServer.get(\"/customer\", { response.send(customers) })\n```\n\nFor very simple operations this might be ok however, it will soon become unmaintainable.\n\n##### Option 2\nDefine route handlers as functions and reference them:\n\n```\nval appServer = AppServer()\n\nappServer.get(\"/customer\", getCustomers)\n```\n\nThis means that your definition of route handlers pretty much becomes a routing table, which is what it should be.\n\nThis is the preferred option. You can then group functions however way you want:\n\n##### Grouping by file\nGroup similar routes in its own file. As Kotlin allows top level functions, you do not need to have a class to group functions.\nAs such, you could have a file name *CustomerRouteHandlers.kt* for instance with:\n\n```kotlin\nval getCustomers = routeHandler {\n\n  response.send(customers)\n\n}\n\nval getCustomerById = routeHandler {\n  ...\n}\n```\n\nrouteHandler is a syntatic sugar to define the type of the route handler. You could also have writte that as:\n\n```kotlin\nval getCustomers : RouteHandler.() -\u003e Unit = {\n\n  response.send(customers)\n\n}\n\nval getCustomerById  : RouteHandler.() -\u003e Unit = {\n  ...\n}\n```\n\n##### Grouping by class\n\nIf for some reason you want to group by class, you can do so. Best way is to use a companion object\n\n```kotlin\nclass CustomerRoutes {\n\n    companion object {\n\n        val createCustomer = routeHandler {\n\n        }\n\n    }\n\n}\n```\n\n*Note*\nKotlin allows you to refer to a function by name using :: syntax. In principle you could also do:\n```kotlin\nappServer.get(\"/customer\", ::getCustomer)\n```\nand not require an explicit variable declaration for the function. However, this currently does not work with extension functions\nbut hopefully will in the future.\n\n\n\n\n### Interceptors ###\nIn addition to handlers, Wasabi also has interceptors. These allow you to intercept a request and decide whether you\nwant it to continue or not (returning false would stop processing). Since you have access to both the request and response, you can\ndo whatever you need. Think of interceptors as a way to add functionality to every request, or a those matching a certain route pattern.\nSome frameworks have popularized the term *middleware* to refer to something that intercepts a request/response.\nI do not agree with such a broad and somewhat ambiguous term. I like to name things as close to what they actually do.\nAn interceptor implements the following interface\n\n```kotlin\ninterface Interceptor {\n  fun intercept(request: Request, response: Response): Boolean\n}\n```\n\nYou return true if you want the process to continue or false if you want to interrupt the request.\n\nTo add an interceptor to the application, you use\n\n```kotlin\nserver.interceptor(MyInterceptor(), path, position)\n```\n\nwhere path can be a specific route or *** to match all routes. Position indicates when the intercept occurs. Possible positions are\n\n```kotlin\n enum class InterceptOn {\n     PreRequest\n     PreExecution\n     PostExecution\n     PostRequest\n     Error\n }\n```\n\nOut of the box, the following interceptors are available\n\n* BasicAuthenticationInterceptor: Basic authentication\n* ContentNegotiationInterceptor: Automatic Content negotation\n* FavIconInterceptor: Support for favicon\n* StaticFileInterceptor: Support for serving static files\n* LoggingInterceptor: Logging\n* FileBasedErrorInterceptor: Customize error messages based on convention (e.g. 404.html)\n* SessionManagementInterceptor: Session Management support\n\nMost interceptor add extension methods to *AppServer* to make them easier (and more descriptive) to use. For instance the\n*ContentNegotiationInterceptor* and *StaticFileInterceptor*  would be used as so\n\n```kotlin\nval appServer = AppServer()\n\nserver.negotiateContent()\nserver.serveStaticFilesFromFolder(\"/public\")\n```\n## Content Negotiation ##\nWasabi ships with content negotiation out of the box, via a couple of interceptors. In particular:\n\n * ContentNegotiationParserInterceptor (still looking for a better name)\n   Allows you to indicate to Wasabi to not only take into account Accept Headers but also Query Fields and Extensions on documents\n\n * ContentNegotiationInterceptor\n   Does the actual content negotiation, finding the appropriate serializer.\n\n### How it works ###\n\n#### Content Negotiation Parsing ####\nSometimes you want to not only do Content Negotiation using the Accept headers, but also using a query field (for instance *format=json*)\nor extensions to documents (e.g. /customer.json).\n\nThe ContentNegotiationParser allows you to do this. Easiest way to use it is via the extension function:\n\n```kotlin\nserver.parseContentNegotiationHeaders() {\n    onQueryParameter(\"format\")\n    onExtension()\n    onAcceptHeader()\n}\n```\n\nBased on the order in which you pass in onAcceptHeader, onExtension and onQueryParameter defines the priority. Above for instance\nfirst the Query Parameter is taking into account, then extension and lastly the Accept Header\n\n*QueryParameter* defaults to the query parameter *format* but you can pass in a different one. Both *onExtension* and *onQueryParameter*\nalso 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*\n\n#### Automatic Content Negotiation ####\nBy default Content Negotiation is enabled. You can disable it via the AppConfiguration. You can then just send and object you want and Wasabi\nwill automatically serialize it and send it back to the client.\n\n```kotlin\nserver.get(\"/customer/:id\", {\n  val customer = getCustomerById(request.params[\"id\"])\n  response.send(customer)\n}\n```\n\nWasabi will automatically serialize that into Json, Xml or any other media type supported (see Serializers below)\n\n#### Manual ####\nIf you need to manually override Content Negotiation, you can do so using the *negotiate* function on *response*\n\n```kotlin\n   server.get(\"/customer/:id\", {\n\n\n        val customer = getCustomerById(request.params[\"id\"])\n\n        response.negotiate (\n            \"text/html\" with { send(\"Joe Smith\") },\n            \"application/json\" with { send(customer) }\n        }\n\n\n}\n```\n\n*negotiate* signature is:\n\n```kotlin\nfun negotiate(vararg negotiations: Pair\u003cString, Response.() -\u003e Unit\u003e)\n```\n\nYou can pass in an unlimited number of media type, functions. Also, as the function is an extension function to *Response*, you have\naccess to the response functions directly. That is why we can just write *send* as opposed to *response.send*.\n\nNote that even if you use manual content negotiation, Wasabi will still try and serialize the object for you based on the media type.\n\n### Serializers ###\n\nBoth manual as well as automatic Content Negotiation use serializers. Wasabi ships with Json and XML (TODO) serializers. These are\ndefined as a property of the *AppServer*.\n\nEach Serializer can take as parameters a variable number of strings which define regular expressions for the media types it can handle. JsonSerializer\nfor instance takes:\n\n```kotlin\nclass JsonSerializer(): Serializer(\"application/json\", \"application/vnd\\\\.\\\\w*\\\\+json\")\n```\n\n## CORS Support ##\nWasabi provides CORS support via an Interceptor. You can enable CORS support in multiple ways:\n\n* Via AppConfiguration.enableCORSGlobally - which enables CORS for all routes, all verbs, all origins\n* Programatically using server.enableCORSGlobally - same as above\n* Programatically using server.enablesCORS([...COREntry...]) - providing an array of CORSEntry's\n\nEach CORSEntry consists of:\n\n```kotlin\nclass CORSEntry(val path: String = \"*\",\n                       val origins: String = \"*\",\n                       val methods: String = \"GET, POST, PUT, DELETE\",\n                       val headers: String = \"Origin, X-Requested-With, Content-Type, Accept\",\n                       val credentials: String = \"\",\n                       val preflightMaxAge: String = \"\") {\n\n}\n```\n\nwith the corresponding defaults. Obviously you should only have one default.\n\n## Auto Options Support ##\nWasabi can automatically respond to OPTIONS for a specific path. You can enable this in multiple ways:\n\n* Via AppConfiguration.enableAutoOptions\n* Programatically using server.enableAutoOptions - same as above\n\n## Exception Handlers ##\nWasabi allows registration of custom handlers by exception type also. You can register them as follows:\n\n```kotlin\nval appServer = AppServer()\nappServer.exception(MyKewlException::class, {\n    reponse.setStatus(418, \"My brew is not as strong as yours!\")\n    response.send(\"Out of beans: ${exception.message}\")\n})\n```\n\nlike other Wasabi handlers the registered exception handler gets passed the request and response as well as the\nexception thrown.\n\n## WebSocket Support ##\nWasabi has initial WebSocket support which automatically handles websocket handshake and socket upgrade.\nWasabi allows the registration of multiple 'channels' per appserver and you can declare a handler per channel.\nAs our websocket support matures we intend to allow the registration of interceptors and serializers in the \nmanner our HTTP / HTTP/2 pipeline does currently. \n\nExample showing direct reply to the current client. \n\n```kotlin\n    appServer.channel(\"/foo\", {\n        if (frame is TextWebSocketFrame) {\n            val textFrame = frame as TextWebSocketFrame\n            respond(ctx!!.channel(), TextWebSocketFrame(textFrame.text().toUpperCase()))\n        }\n    })\n```\n\nExample showing message broadcast from channel.\n\n```kotlin\n    appServer.channel(\"/foo\", {\n        if (frame is TextWebSocketFrame) {\n            val textFrame = frame as TextWebSocketFrame\n            broadcast(TextWebSocketFrame(textFrame.text().toUpperCase()))\n        }\n    })\n```\n\nExample showing triggering broadcast from webservice.\n\n```kotlin\n    appServer.post(\"/message\", {\n        val message = request.bodyParams[\"message\"]\n        broadcast(\"/foo\", TextWebSocketFrame(message))\n        response.send()\n    })\n```\n\n## Community ##\nWe're mostly hanging out on the #wasabi Channel on [Kotlin's Slack](http://kotlinslackin.herokuapp.com). Join us there for questions and discussions.\n\n\n\n\n### Contributions ###\n\nThere's a lot of work still pending and any help would be appreciated. Pull Requests welcome!\n\nWe have the project building on TeamCity. Click on the Icon to go to the build\n\n\n### License ###\nLicensed under Apache 2 OSS License\n\n\n### Thank you's ###\nBig thank you  to [JProfiler](https://www.ej-technologies.com/products/jprofiler/overview.html) for the OSS license to help improve Wasabi performance\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwasabifx%2Fwasabi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwasabifx%2Fwasabi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwasabifx%2Fwasabi/lists"}