{"id":13806348,"url":"https://github.com/GreenfieldTech/irked","last_synced_at":"2025-05-13T21:32:54.998Z","repository":{"id":22608028,"uuid":"95486262","full_name":"GreenfieldTech/irked","owner":"GreenfieldTech","description":"Controller framework for Vert.x-web with automatic configuration and dynamic discovery.","archived":false,"fork":false,"pushed_at":"2025-05-09T11:43:24.000Z","size":828,"stargazers_count":34,"open_issues_count":0,"forks_count":2,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-05-09T12:35:48.454Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/GreenfieldTech.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2017-06-26T20:23:40.000Z","updated_at":"2025-05-09T11:43:24.000Z","dependencies_parsed_at":"2023-10-16T03:13:39.056Z","dependency_job_id":"9dc5a3c8-1f6f-4b68-8f98-2e9c282d07ca","html_url":"https://github.com/GreenfieldTech/irked","commit_stats":null,"previous_names":[],"tags_count":156,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GreenfieldTech%2Firked","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GreenfieldTech%2Firked/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GreenfieldTech%2Firked/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GreenfieldTech%2Firked/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/GreenfieldTech","download_url":"https://codeload.github.com/GreenfieldTech/irked/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254031417,"owners_count":22002758,"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-08-04T01:01:10.570Z","updated_at":"2025-05-13T21:32:54.969Z","avatar_url":"https://github.com/GreenfieldTech.png","language":"Java","funding_links":[],"categories":["Web Frameworks","开发框架"],"sub_categories":["Web框架"],"readme":"# Irked Vert.x Web Framework 4.x\n\nIrked is a very opinionated framework for configuring Vert.x-web routing and call dispatch.\n\nIt allows you to write your REST API code without writing routing boiler plate by leveraging\nannotations, auto-discovery through reflection and optionally (if you're into that as well)\ndependency injection.\n\nThis version supports Vert.x 4. To use with earlier Vert.x versions, try Irked 2 for Vert.x 3.9\nsupport or Irked 1 for earlier versions (Vert.x 3.9 has changed its API in a non-backward compatible\nway - method return types were changed - which required that 1 + 2 split).\n\nOther than different backward compatibility, Irked versions are essentially the same with bug fixes\nported to all releases.\n\n## Installation\n\nIrked is available from the [Maven Central Repository](https://central.sonatype.com/artifact/tech.greenfield/irked-vertx/4.5.14.2).\n\nIf using Maven, add Irked as a dependency in your `pom.xml` file:\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003etech.greenfield\u003c/groupId\u003e\n    \u003cartifactId\u003eirked-vertx\u003c/artifactId\u003e\n    \u003cversion\u003e4.5.14.2\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\nFor other build tools, see the Maven Central website for the syntax, but it generally\nboils down to just using `tech.greenfield:irked-vertx:4.5.14.2` as the dependency string.\n\n## Quick Start\n\nYou may want to take a look at the example application at [`src/example/java/tech/greenfield/vertx/irked/example/App.java`](src/example/java/tech/greenfield/vertx/irked/example/App.java) which shows how to create a new Vert.x Verticle using an Irked `Router` and a few very simple APIs. Then you may want to read the rest of this document for explanations, rationale and more complex API examples.\n\nTo run the example application, after compiling (for example, using `mvn compile`) run it with your full Vert.x 4.5.14 installation:\n\n```\nvertx run -cp target/classes/ tech.greenfield.vertx.irked.example.App\n```\n\nOr, alternatively, using the Vert.x JAR dependencies in the Irked maven project:\n\n```\nmvn exec:exec -Dexec.executable=java -Dexec.args=\"-cp %classpath io.vertx.core.Launcher run tech.greenfield.vertx.irked.example.App\"\n```\n\n## Usage\n\nUnder Irked we use the concept of a \"Controller\" - a class whose fields and methods are used as\nhandlers for routes and that will handle incoming HTTP requests from Vert.x-web.\n\nA \"master controller\" is created to define the root of the URI hierarchy - all configured routes\non that controller will be parsed relative to the root of the host.\n\n### Setup and Simple Routing\n\nTo publish routes to the server's \"Request Handler\", create your controller class by extending the\nirked `Controller` class, define fields or methods to handle HTTP requests and annotate them with\nthe route matching logic that you want Irked to configure for each handler.\n\n- Most often you'd want to just match on HTTP method and URI, for which Irked offers \n  annotations of the form `@Method(\"/path\")`, such as `@Get(\"/foo\")`.\n- You can also use the annotation `@Endpoint(\"/path\")` to match on all methods\n  (this very is useful to mount sub-controllers, as detailed below).\n- The path argument for the both method-specific and `@Endpoint` annotations is optional \n  and omitting it will match requests for all URIs.\n\n#### An Example Controller\n\n```java\npackage com.example.api;\n\nimport tech.greenfield.vertx.irked.*;\nimport tech.greenfield.vertx.irked.status.*;\nimport tech.greenfield.vertx.irked.annotations.*;\n\nclass Root extends Controller {\n\n    @Get(\"/\")\n    Handler\u003cRoutingContext\u003e index = r -\u003e {\n        // classic Vert.x RoutingContext usage\n        r.response().setStatusCode(200).end(\"Hello World!\");\n    };\n\n    @Post(\"/\")\n    void create(Request r) {\n        // the irked Request object offers some useful helper methods over the\n        // standard Vert.x RoutingContext\n        r.send(new BadRequest(\"Creating resources is not yet implemented\"));\n    }\n    \n    @Put(\"/update\")\n    String update(Request r) {\n        return \"Updated completed\";\n    }\n}\n```\n\n#### Initializing\n\nAfter creating your set of `Controller` implementations, deploy them to Vert.x by setting up\na `Verticle` like you would do for a [Vert.x-web Router](https://vertx.io/docs/vertx-web/java/#_basic_vert_x_web_concepts),\nbut use Irked to create a router from your root controller - and set that as the request handler.\n\n#### A Vert.x Web HTTP Server Example\n\n```java\nRouter router = Irked.router(vertx).with(new com.example.api.Root());\nvertx.createHttpServer().requestHandler(router).listen(8080);\n```\n\n### Configuration Errors\n\nSometimes humans make mistakes, it happens. When Irked is asked to setup your controllers\n(when calling `with()` or one of the other setup methods), it scans your configuration\n(the controller classes annotations) and if it detects a configuration that cannot be\nexecuted - for example, handlers with too few or too many parameters - it will throw\nan `InvalidRouteConfiguration` exception with as much details as appropriate. This\nhappens during the Vert.x setup stage and before the HTTP server request handler is set\nup. We make every attempt to not cause exceptions to be thrown during actual request\nprocessing, and any such generated by the Irked implementation will be considered bugs and\nwill be fixed.\n\n### Sub Controllers\n\nComplex routing topologies can be implemented by \"mounting\" sub-controllers under\nthe main controller - by setting fields to additional `Controller` implementations and annotating\nthem with the `@Endpoint` annotation with the URI set to the endpoint you want your sub-controller\nto be accessible under.\n\n### Main and Sub Controllers Example\n\n```java\npackage com.example.api;\n\nimport tech.greenfield.vertx.irked.*;\nimport tech.greenfield.vertx.irked.annotations.*;\n\nclass Root extends Controller {\n\n    @Endpoint(\"/blocks\") // the block API handles all requests that start with \"/blocks/\"\n    BlockApi blocks = new BlockApi();\n\n}\n```\n\n```java\npackage com.example.api;\n\nimport tech.greenfield.vertx.irked.*;\nimport tech.greenfield.vertx.irked.annotations.*;\n\nclass BlockApi extends Controller {\n\n    @Get(\"/:id\") // handle requests like \"GET /blocks/identifier\"\n    Handler\u003cRequest\u003e retrieve = r -\u003e {\n        // irked supports Vert.x-web path parameters \n        r.send(loadBlock(r.pathParam(\"id\")));\n    };\n}\n```\n\n### Request Context Re-programming\n\nAs hinted above, irked supports path parameters using Vert.x-web, but \n[unlike Vert.x-web's sub-router](https://github.com/vert-x3/vertx-web/blob/b778f450bc4e0e928f8b6c761a376b5ab9f24151/vertx-web/src/main/java/io/vertx/ext/web/impl/RouteImpl.java#L156),\nirked controllers support path parameters everywhere, including as base paths for mounting sub-controllers.\n\nAs a result, a sub-controller might be interested in reading data from a path parameter defined in\na parent controller, such that the sub-controller has no control over the definition. To promote\nobject oriented programming with good encapsulation, irked allows parent controllers to provide\naccess to parameter (and other) data by \"re-programming\" the routing context that is passed to\nsub-controllers.\n\nA parent controller can define path parameters and then extract the data and hand it down to local\nhandlers and sub-controllers through a well defined API, by overriding the\n`Controller.getRequestContext()` method.\n\nThis functionality is also just useful to DRY repeatable data access pattern by encapsulating them\nin a request context type.\n\n#### Request Context Re-programming Example\n\n```java\npackage com.example.api;\n\nimport tech.greenfield.vertx.irked.*;\nimport tech.greenfield.vertx.irked.annotations.*;\n\nclass MyRequest extends Request {\n\n    String id;\n\n    public MyRequest(Request req) {\n        super(req);\n        id = req.pathParam(\"id\");\n    }\n\n    public String getId() {\n        return id;\n    }\n\n}\n```\n\n```java\npackage com.example.api;\n\nimport tech.greenfield.vertx.irked.*;\nimport tech.greenfield.vertx.irked.status.*;\nimport tech.greenfield.vertx.irked.annotations.*; \n\nclass Root extends Controller {\n\n    @Get(\"/:id\")\n    void report(MyRequest r) {\n        r.response(new OK()).end(createReport(r.getId()));\n    }\n\n    @Endpoint(\"/:id/blocks\") // \":id\" is read by the MyRequest class\n    BlockApi blocks = new BlockApi();\n\n    @Override\n    protected MyRequest getRequestContext(Request req) {\n        return new MyRequest(req);\n    }\n}\n```\n\n```java\npackage com.example.api;\n\nimport tech.greenfield.vertx.irked.*;\nimport tech.greenfield.vertx.irked.annotations.*;\n\nclass BlockApi extends Controller {\n\n    @Get(\"/\")\n    Handler\u003cMyRequest\u003e retrieve = r -\u003e {\n        r.send(getAllBlocksFor(r.getId())); // read the identifier field defined by Root and MyRequest\n    };\n}\n```\n\n#### Handler-specific Request Context Re-Programming\n\nBy having a controller implement `getRequestContext()` it can create a specific Request sub-type to be used \nby all handlers in that controller or controllers mounted under it. Handlers can also require additional specialization\nof the request context, on a per-handler basis. That allows different handlers to use different\nspecializations - if it makes sense. Irked will automatically construct such specializations using one\nof two methods:\n1. If the specialization can be trivially constructed from the current request context - either\n   `Request` or a controller-level specialization created by a custom `getRequestContext()` - i.e. the specialized\n   Request sub-type class has a constructor that takes a single such parameter.\n2. If the current controller class provides a creator method that accepts a `Request` instance (not a sub-type) and\n   returns the needed type.\n\nBoth methods are demonstrated in the following examples:\n\n##### Trivially Constructed Request Context Specialization Example\n\n```java\npackage com.example.api;\n\nimport tech.greenfield.vertx.irked.*;\nimport tech.greenfield.vertx.irked.status.*;\nimport tech.greenfield.vertx.irked.annotations.*;\nimport com.example.api.Hasher;\n\nclass Api extends Controller {\n\n    public static class MyReqeust extends Request {\n        String id;\n        String context;\n\n        public MyRequest(Request req, String context) {\n              super(req);\n              id = req.pathParam(\"id\");\n              this.context = context\n        }\n        \n        public String getId() {\n            return id;\n        }\n        \n        public String getContext() {\n            return context;\n        }\n    }\n\n    public Request getRequestContext(Request req) {\n        return new MyRequest(req, \"foo\");\n    }\n    \n    @Get(\"/:id/context\")\n    Handler\u003cMyRequest\u003e getContext = r -\u003e r.sendContent(r.getContext());\n    \n    public static class MyHashingRequest extends MyRequest {\n        String hash;\n        \n        public MyHashingRequest(MyRequest req) {\n            super(req);\n            hash = Hasher.hash(req.getId());\n        }\n        \n        public String getHash() {\n            return hash;\n        }\n    }\n    \n    @Get(\"/:id/hash\")\n    Handler\u003cMyHashingRequest\u003e calcHash = req -\u003e req.sendContent(req.getHash());\n\n    // Method handlers are also supported\n    @Get(\"/:id/hash2\")\n    public void generateHash(MyHashingRequest req) {\n        req.sendContent(req.getHash());\n    }\n\n}\n```\n\nIn the above example, when entering the controller, Irked calls `getRequestContext()` to generate\nthe current request context type - which is `MyRequest` in this case - then when it resolves the\nroute `/:id/hash` and needs to call `generateHash()` - it identifies `MyHashingRequest` a request context specialization that can be constructed from the current request context type (`MyRequest`)\nand does so.\n\nBecause this example uses embedded classes for the request contexts, it is important that these can\nbe statically constructed, as Irked cannot currently construct non-static embedded classes.\n\n##### Provided Request Context Specialization Example\n\n```java\npackage com.example.api;\n\nimport tech.greenfield.vertx.irked.*;\nimport tech.greenfield.vertx.irked.status.*;\nimport tech.greenfield.vertx.irked.annotations.*;\nimport com.example.api.Hasher;\n\nclass Api extends Controller {\n\n    public class MyReqeust extends Request {\n        String id;\n\n        public MyRequest(Request req) {\n            \tsuper(req);\n            \tid = req.pathParam(\"id\");\n        }\n        \n        public String getId() {\n            return id;\n        }\n\t}\n\t\n    public Request getRequestContext(Request req) {\n        return new MyRequest(req);\n    }\n    \n    public class MyHashingRequest extends MyRequest {\n        String hash;\n        \n        public MyHashingRequest(MyRequest req, String secret) {\n            super(req);\n            hash = Hasher.hash(secret + req.getId());\n        }\n        \n        public String getHash() {\n            return hash;\n        }\n    }\n    \n    public MyHashingRequest createSecretHasherContext(Request req) {\n        return new MyHashingRequest((MyRequest)req, \"SECRET\");\n    }\n    \n    @Get(\"/:id/hash\")\n    Handler\u003cMyHashingRequest\u003e calcHash = req -\u003e req.sendContent(req.getHash());\n\n    // Method handlers are also supported\n    @Get(\"/:id/hash2\")\n    public void generateHash(MyHashingRequest req) {\n        req.sendContent(req.getHash());\n    }\n}\n```\n\nIn the above example, when entering the controller, Irked calls `getRequestContext()` to generate\nthe current request context type - which is `MyRequest` in this case - then when it resolves the\nroute `/:id/hash` and needs to call `generateHash()` - it identifies `MyHashRequest` a request context specialization that _cannot_ be constructed from just the current request context type\n(`MyRequest`) but there is a provider method that can create that specialization, so it calls it\nand then passes the generated instance as the request context to `generateHash()`.\n\nBecause Irked attempts to do reflection-based lookups during setup - instead of in run time - then it\ncan't know the exact current request context type, and it can only locate provider methods that\ntake a top-level `Request` object. In this case the local provider implementation is adjacent to the\n`getRequestContext()` implementation and can safely assume the specific instance type it will get.\n\n### Cascading Request Handling\n\nSometimes its useful to have the multiple handlers handle the same request - for example you\nmay have a REST API that supports both PUT requests to update data and GET requests on the same\nURI to retrieve such data. Supposed the response to the PUT request looks identical to the response\nfor a GET request - it just shows how the data looks after the update - so wouldn't it be better\nif the same handler that handles the GET request also handles the output for the PUT request?\n\nFor example:\n\n```java\npackage com.example.api;\n\nimport tech.greenfield.vertx.irked.*;\nimport tech.greenfield.vertx.irked.status.*;\nimport tech.greenfield.vertx.irked.annotations.*; \n\nclass Root extends Controller {\n\n    @Endpoint // set up body reading middle-ware for all requests (see more below)\n    BodyHandler bodyHandler = BodyHandler.create();\n\n    @Put(\"/:id\")\n    WebHandler update = r -\u003e {\n        // start an async operation to store the new data\n        store(r.pathParam(\"id\"), r.getBodyAsJson())\n        .onSuccess(v -\u003e { // Instead of sending the response\n            r.next(); // let the next handler do it\n        })\n        .recover(err -\u003e { // if storing failed\n            r.sendError(new InternalServerError(err)); // send an 500 error\n        });\n    };\n\n    @Put(\"/:id\")\n    @Get(\"/:id\")\n    WebHandler retrieve = r -\u003e {\n        load(r.pathParam(\"id\"))\n        .compose(data -\u003e r.send(data))\n        .recover(err -\u003e r.sendError(new InternalServerError(err)))\n    };\n\n}\n```\n\nYou can, of course, pass data between handlers using the `RoutingContext`'s `put()`, `get()` and\n`data()` methods as you can normally do with Vert.x-web.\n\nThis way works well when configuring routing using controller fields - Irked uses Java\nreflection to read the order the fields are defined in the source code and configure\nthe handlers in the correct order taking, advantage of implicit Vert.x-web route order -\nbut this naive \"definition order\" doesn't work for controller methods due to limitations\nof Java reflection. For explicit ordering, useful for both methods and fields, see\n[Explicit handler ordering](#explicit-handler-ordering) below.\n\n### Routing methods with dynamic parameters\n\nWhen using methods for routing handling, Irked offers automatic conversion of path parameters into method parameters:\nby creating handler methods that accept - in addition to the request context - a set of trivially text convertible\nparameters, Irked will locate appropriately labeled path parameters, converted them to the desired Java types and provide\nthe values in the method invocation:\n\n```java\n@Get(\"/catalog/:producer/:id\")\npublic void getCatalogItem(Request r, String producer, Integer id) {\n    dao.findCatalog(producer).compose(cat -\u003e cat.findItem(id))\n            .compose(r::send)\n            .recover(err -\u003e r.sendError(new InternalServerError(err)));\n}\n```\n\nPlease keep in mind the following limitations:\n\n- Irked knows how to convert the following parameter types: `Boolean`, `String`, `Integer`, `Long`, `Float`, `Double`,\n  `BigDecimal` and `Instant`. If the value cannot be parsed correctly to the required type, Irked will set that\n  parameter value to `null`. In any case, the raw value can be retrieved using the regular path parameter lookup methods.\n- Primitive parameter types are not supported - i.e. `Integer` is supported but `int` is not, otherwise Irked cannot\n  report parse failures. If an unsupported parameter type is found during configuration, it is an error and Irked will\n  throw an `InvalidRouteConfiguration` exception. Regardless, Irked will not cause exceptions to be thrown during\n  actual request processing.\n- Handlers can be annotated with multiple annotations with different paths and path parameters (it is not recommended,\n  but is supported), and as such the parameters can match to any path parameter on any path annotation set on the method.\n  Irked will pass `null` for any parameter that can't be matched on the current active route. If during configuration\n  Irked cannot locate the requested parameter in any of the configured path annotations, it is an error and Irked will\n  throw an `InvalidRouteConfiguration` exception.\n- Irked has two main strategies for matching parameters - either by matching the parameter name as reported by Java\n  reflection, or by matching a parameter annotation called `Name` or `Named`. Irked is not bound to a specific annotation\n  implementation, it will use whatever annotation type you are already using, or you can use Irked own\n  `tech.greenfield.vertx.irked.annotations.Name`. The parameter name strategy only works if the Java class was compiled\n  with debug symbols including parameter names - you can do that by adding the `-parameters` option to the `javac`\n  command line, or - if using Maven's Java compiler plugin, by setting its configuration to\n  `\u003cconfiguration\u003e\u003cparameters\u003etrue\u003c/parameters\u003e\u003c/configuration\u003e`.\n\nWhen building without parameter name debug symbols, the above example would need to have the method declared\nlike so:\n\n```java\n@Get(\"/catalog/:producer/:id\")\npublic void getCatalogItem(Request r, @Name(\"producer\") String producer, @Name(\"id\") Integer id) {\n```\n\n### Routing Methods Return Values (aka \"lazy methods\")\n\nWhen using methods for routing handling, Irked can handle the sending itself - you don't need to call any of the `Request`\nsending methods.\n\nIf the return value from a method is a Vert.x `Future`, then Irked will chain on an `.onComplete(Request::sendOrFail)`,\notherwise Irked will use `Request.send(result)` with the result.\n\nPlease note that if a routing method returns a `null`,\nthen Irked will send that as an `application/json` result whose value is a JSON `null`.\n\n### Handle Failures\n\nIt is often useful to move failure handling away from the request handler - to keep the code clean\nand unify error handling which is often very repetitive. Irked supports \n[Vert.x-web's error handling](http://vertx.io/docs/vertx-web/js/#_error_handling) using the `@OnFail` annotation that you can assign to a request handler, which makes it so that the handler is only called for requests for which `fail()` has been called. \n\nNote: the request failure handler still needs to be configured properly for a URI and HTTP method. \nWe often find it useful to use the default (no path) `@Endpoint` annotation to configure a default\nfailure handler (this is equivalent to using the Vert.x-web `Router.route()` API to create an any-method/any-path\nhandler) - though multiple and specific failure handlers would work fine.\n\n#### A Failure Handler Example\n\n```java\npackage com.example.api;\n\nimport tech.greenfield.vertx.irked.*;\nimport tech.greenfield.vertx.irked.status.*;\nimport tech.greenfield.vertx.irked.annotations.*; \n\nclass Root extends Controller {\n\n    @Get(\"/foo\")\n    WebHandler fooAPI = r -\u003e {\n        loadFoo().compose(r::send).onFailure(r::fail);\n    };\n\n    @Post(\"/foo\")\n    WebHandler fooAPI = r -\u003e {\n        r.fail(new UnsupportedOperationException(\"Not implemented yet\"));\n    };\n\n    @OnFail\n    @Endpoint // catch all failures that haven't been dealt with yet\n    void failureHandler(Request r) {\n        r.sendError(new InternalServerError(r.failure()));\n    }\n}\n```\n\nIrked `Request.sendError()` works with HTTP status codes classes and will create an HTTP response with the appropriate error status and an `application/json` body with a JSON object containing the fields \"`status`\" set to `false` and \"`message`\" set to the exception's detail message. The response's content can be further controlled by instead using one of the `Request.sendJSON()` or `Request.sendContent()` methods that take an `HttpError` parameter.\n\nAlso see the tips section below for a more complex failure handler that may be useful.\n\n#### Status Code Specific and Exception Specific Failure Handlers\n\nThe `@OnFail` annotation allows customizing the failure handler to only handle specific HTTP status codes (when propagated using the `RoutingContext.fail(int)` or `RoutingContext.fail(int, Throwable)` methods) or only handle specific exceptions (when propagated using the `RoutingContext.fail(int, Throwable)` or `RoutingContext.fail(Throwable)`). This allows for creating smaller failure handlers, that - like `catch` clauses - only handle a specific exception or status.\n\n##### A Specific Exception Failure Handler Example\n\n```java\nclass Root extends Controller {\n    @Post(\"/foo\")\n    WebHandler fooAPI = r -\u003e {\n        if (database == null)\n            throw new ConfigurationException(\"Database is not ready\");\n        database.store(r.body().asJsonObject());\n    };\n\n    @OnFail(exception = ConfigurationException.class)\n    @Post(\"/foo\")\n    WebHandler fooDecodeError = r -\u003e r.sendContent(\"Please wait until the database is ready\", new BadRequest());\n\n    @OnFail(exception = DataAccessException.class)\n    @Post(\"/foo\")\n    WebHandler fooDecodeError = r -\u003e r.sendContent(\"Error storing data: \" + r.failure().getMessage(), new InternalServerError());\n}\n```\n\nIt is also possible to extract the caught exception instance in the handler using Irked\n`Request.findFailure()` method:\n\n```java\n@OnFail(exception = DataAccessException.class)\n@Endpoint\nWebHandler blobDaoError = r -\u003e r.sendContent(\"Failed to insert blob into \" +\n        r.findFailure(DataAccessException.class).getColumn(), new BadRequest());\n```\n\nAnother useful way to get access to the caught failure is by using a method with dynamic parameters (see\n[Routing methods with dynamic parameters](#routing-methods-with-dynamic-parameters) above):\n\n```java\n@OnFail(exception = DataAccessException.class)\n@Endpoint\npublic blobDaoError(Request r, DataAccessException err) {\n    r.sendContent(\"Failed to insert blob into \" + err.getColumn(), new BadRequest());\n}\n```\n\nThe exception type can be any of the exception types specified for the `@OnFail(exception)` annotations on the method - there can\nbe more than one and there can be more exception parameters, or you can request a super type of several registered\n`@OnFail(exception)` annotations - Irked will deliver the handled exception to the correct parameter and send `null` to all\nother exception type parameters, if the method has more than one. It is a configuration error to request a dynamic\nparameter with an exception type that is not a registered `@OnFail(exception)` or a super class of one - and Irked\nwill throw an `InvalidRouteConfiguration` exception during router setup, if this situation occurs.\n\n### Request Content-Type Specific Handlers\n\nIrked supports the Vert.x-web `consumes()` filter to specify request handlers\nthat only handle specific request content-types, using the `@Consumes`\nannotation. Specify it like URI annotations with the expected request content type. Like the `consumes()` method, it supports wild cards.\n\n#### A Request Content-Type Handler Example\n\n```java\n@Post(\"/upload\")\n@Consumes(\"multipart/form-data\")\nWebHandler fileUpload = r -\u003e {\n    for (FileUpload f : r.fileUploads()) saveFile(f);\n    r.send(\"Uploaded!\");\n};\n```\n\n### Async Processing\n\nIrked contains a few helpers for using Promise-style APIs, such as Vert.x `Promise`/`Future` or Java 8's\n`CompletableFuture`, to asynchronously process requests using friendly method references, most notably\n`Request.send()` to send responses and `Request.handleFailure` to forward \"exceptional completion\"\nExceptions to the failure handlers, or `Request.next()` which makes it easier to attach `RoutingContext.next()`\nto `Future.onSuccess()` handlers.\n\nPlease note that helper methods that can fail will return a `Future` (for example, `Request.send()` uses\n`io.vertx.core.json` to encode JSON responses and that can call into user code that can fail). These should be used\nin a Vert.x Future chain, in a `Future.compose()` step, so that if there was a failure - it will becaught by a downstream\nerror handler. For example:\n\n```java\n@Get(\"/\")\nWebHandler getSomething = r -\u003e loadSomething()\n    .compose(r::send)\n    .onFailure(r::handleFailure);\n```\n\nIn this case, `Request.send()` may return a failed future (if `loadSomething()` loaded an object that cannot be safely\nserialized to JSON) that will be handled by `Request.handleFailure()`. On the other hand, `Reqeuest.handleFailure()`\ndoes not return a `Future` as it cannot fail (or at least, it internally calls `RoutingContext.fail()` to handle the\nfailure, and it can also do that to handle its own internal failures).\n\n#### An Asynchronous Processing Example\n\n```java\nFuture\u003cList\u003cPojoType\u003e\u003e loadSomeRecords() {\n    // … access a database asynchronously to load some POJOs\n}\n\n@Get(\"/\")\nWebHandler catalog = r -\u003e // we don't even need curly braces\n    loadSomeRecords() // fetch records\n    .map(l -\u003e l.stream() // stream loaded records as POJOs\n            .map(JsonObject::mapFrom) // bean map each record to a JSON object\n            .collect(JsonArray::new, JsonArray::add, JsonArray::addAll)) // collect everything to a JSON array\n    .compose(r::send) // send the list to the client\n    .onFailure(r::handleFailure); // capture any exceptions and forward to the failure handler\n```\n\nYou can review the Irked unit test [`TestAsyncSending.java`](src/test/java/tech/greenfield/vertx/irked/TestAsyncSending.java) for more examples.\n\n### WebSockets and SockJS\n\nThere are several implementation strategies to handle WebSockets under Irked controllers. Irked offers an \"opinionated\" API that gets out of the way of the developer and allows them to leverage other Irked facilities, such as cascading requests and custom request contexts - as detailed below.\n\nIn the case that the developer would prefer lower level access to the WebSocket upgrade protocol, or use the more complex Sock.JS implementation offered by Vert.x-web - this document includes examples on how to achieve both.\n\n#### Handle individual WebSocket messages\n\nBy adding the `@WebSocket(path)` annotation to a handler, the handler will be called once for each message received on a WebSocket connected on the specified path. The WebSocket message handler will receive a `WebSocketMessage` instance provided by Irked.\n\nInstances of this class implement all the method from the `Buffer` class to access the message content, a set of `reply()` method to easily send responses to the received message as well as access to both the original `ServerWebSocket` as well as the original `Request` objects that handle this message - these can be used to track state and communicate information from the WebSocket handshake process\n(for example as generated by a `getRequestContext()` implementation).\n\n##### Example\n\nThis example shows how an incoming WebSocket request can be checked for authorization before handing the request to the WebSocket handler, using cascading requests. The `WebHandler` will be called once for each WebSocket handshake. It can also add data to the request using Vert.x `put()` facility that can be accessed from the `MessageHandler` using `WebSocketMessage.request()`.\n\n```java\n@Get(\"/websocket\")\nWebHandler authIncoming = r -\u003e { // called once for each new WebSocket connection\n  if (!isAuthorized(r.request().getHeader(\"Authorization\"))) r.fail(new Unauthorized());\n  else r.next();\n};\n\n@WebSocket(\"/websocket\")\nMessageHandler handler = m -\u003e { // called for each client message sent in a connection\n  m.reply(\"Hi \" + m.request().session().get(\"name\") + \", you said: \" + m.toString());\n};\n```\n\nWhen using the method handlers, you can also ask for a specific `Request` implementation, to take advantage of typed request contexts, as generated by an appropriate `getRequestContext()` implementation. A custom request context can maintain application state such as the current user.\n\n```java\n@WebSocket(\"/websocket\")\nvoid messageHandler(UserContextRequest req, WebSocketMessage msg) {\n  msg.reply(req.doUserInteraction(msg));\n}\n```\n\n#### SockJS service\n\nIf you are interested in a [Sock.JS](http://sockjs.org) server implementation, Vert.x-web offers `SockJSHandler` that\ncan be mounted directly in an Irked controller as any other Vert.x middle-ware (see below for more about middle-ware):\n\n```java\n@Get(\"/listener\")\nSockJSHandler listener = SockJSHandler.create(vertx).socketHandler(sock -\u003e {\n    sock.handler(buf -\u003e {\n        sock.write(\"You said: \" + buf.toString().trim() + \"\\n\");\n    });\n});\n```\n\n#### Vert.x Core WebSocket API\n\nThere are two main implementations for WebSocket handling under Vert.x Core, as documented in the [WebSocket section of the core manual](http://vertx.io/docs/vertx-core/java/#_websockets):\n\n1. Adding a handler for the server's `websocketStream()`: Irked doesn't support this method directly but\n   you can always add the handler when initializing the server, before or after setting up Irked. Please note that using \n   this method blocks any other WebSocket implementation strategy suggested in this document - the WebSocket Stream handler\n   will consume all incoming connection upgrade requests regardless of the path these target.\n2. Upgrading a request: this can be done in an Irked controller and Irked offers some minimal support to help you\n   manage the process, with the `Request.needUpgrade()` method to help you detect upgrade requests. \n\nThe following example shows how to handle the WebSocket upgrade request and register event handlers to the created Vert.x WebSocket implementation class.\nAs you can see there is quite a bit of heavy lifting and you need to work harder to maintain context and state, compared to Irked `WebSocketMessage` API\nand programmable request contexts.\n\n```java\n@Get(\"/websocket\")\nWebHandler websocketStart = r -\u003e {\n  if (!r.needUpgrade(\"websocket\"))\n    throw new BadRequest(\"This URL only accepts WebSocket connections\").unchecked();\n  r.request().toWebSocket()\n    .onFailure(t -\u003e r.fail(new InternalServerError(\"Failed to upgrade to WebSocket\")))\n    .onSuccess(ws -\u003e {\n      ws.binaryMessageHandler(msg -\u003e handleBinaryMessage(ws, msg));\n      ws.textMessageHandler(msg -\u003e handleTextMessage(ws, msg));\n      ws.exceptionHandler(err -\u003e handleErrors(ws, err));\n    });\n};\n\nprivate handleBinaryMessage(ServerWebSocket ws, Buffer message) {\n  // ...\n}\n\nprivate handleTextMessage(ServerWebSocket ws, String message) {\n  // ...\n}\n\nprivate handleErrors(ServerWebSocket ws, Throwable error) {\n  // ...\n}\n```\n\n### Tips\n\n#### Mounting Middle-Ware\n\nUnder Vert.x its often useful to have a \"middle-ware\" that processes requests before passing\ncontrol back to your application, such as the Vert.x-web [`BodyHandler`](https://vertx.io/docs/apidocs/io/vertx/ext/web/handler/BodyHandler.html)\nthat reads the HTTP request body and handles all kinds of body formats for you, the\n[`LoggerHandler`](https://vertx.io/docs/apidocs/io/vertx/ext/web/handler/LoggerHandler.html)\nthat automatically logs web requests to an Apache style log,\nthe [`CorsHandler`](https://vertx.io/docs/apidocs/io/vertx/ext/web/handler/CorsHandler.html)\nthat lets you easily configure [CORS](https://fetch.spec.whatwg.org/#http-cors-protocol) rules,\n[and many others](https://vertx.io/docs/apidocs/io/vertx/ext/web/handler/package-summary.html).\n\nThis type of middle-ware can be easily used in irked by registering it on a catch all end-point,\nvery similar to how you set it up using the Vert.x-web's `Router` implementation. In your root\ncontroller, add a field - at the top of the class definition - like this:\n\n```java\n@Endpoint // match all methods and all paths\nBodyHandler bodyHandler = BodyHandler.create();\n```\n\nThis will cause all requests to first be captured by the `BodyHandler` before being passed to\nother handlers.\n\n#### Easily Pass Business Logic Errors To Clients\n\nSometimes it is necessary for the REST API to actually generate error responses to communicate\nerroneous conditions to the client - such as missing authentication, invalid input, etc - using\nthe HTTP semantics by returning a response with some standard non-OK HTTP status.\n\nIn such cases, instead of hand crafting a response and doing a collection of `if...else`s to\nmake sure processing doesn't continue, irked allows you to take advantage of Java exception \nhandling and built-in Vert.x-web failure handling functionality to make this a breeze:\n\nFor example a handler might want to to signal that the expected content isn't there by returning\na 404 Not Found error:\n\n```java\n@Get(\"/:id\")\nHandler\u003cRequest\u003e retrieve = r -\u003e {\n    if (!existsItem(r.pathParam(\"id\")))\n        throw new NotFound(\"No such item!\").unchecked();\n    r.send(load(r.pathParam(\"id\")));\n}\n```\n\nThe `HttpError.unchecked()` method wraps the business logic's HTTP status exception with a\n`RuntimeException` so it can jump out of lambdas and other non-declaring code easily without\nboiler-plate exception handling. The Irked request handling glue will pick up the exception and \ndeliver it to an appropriate `@OnFail` handler.\n\nThen the `@OnFail` handler can be configured to automatically forward this status to the client:\n\n```java\n@OnFail\n@Endpoint\nHandler\u003cRequest\u003e failureHandler = r -\u003e {\n    r.sendError(HttpError.toHttpError(r));\n};\n```\n\nThe `HttpError.toHttpError()` helper method detects `RuntimeException`s and unwrap their content\nautomatically. If the underlying cause is an `HttpError`, it will deliver it to the client using\n`Request.sendError()`, otherwise it will create an `InternalServerError` exception (HTTP status 500)\nthat contains the unexpected exception, which will also be reported using `Request.sendError()`.\n\nBecause this use case is so useful, there's even a short-hand for this:\n\n```\n@OnFail\n@Endpoint\nWebHandler failureHandler = Request.failureHandler();\n```\n\n##### \"OK\" Exceptions\n\nBy the way, it is possible to use the throwable `HttpError` types to `throw` any kind of HTTP status,\nincluding a \"200 OK\", like this: `throw new OK().unchecked()`.\n\n##### Declare Thrown Exceptions\n\nIf your controllers uses method handlers, and you prefer not to use \"unchecked\" `HttpError`s, you can also declare thrown exceptions generally (declare throwing `HttpError`) or specific errors:\n\n```java\n@Get(\"/:id\")\nvoid getTheStuff(MyRequest r) throws Unauthorized {\n    if (!r.authorizedToGetStuff())\n        throw new Unauthorized(\"You are not authorized to get our stuff\");\n    r.send(r.theStuff());\n}\n```\n\nIrked will gladly setup such method handlers and will forward any exceptions they throw to the failure handlers.\n\n#### Specify Custom Headers\n\nSometimes a response needs to include custom headers, for example setting the `Location` header for\na \"302 Found\" response. Irked `Request` objects offer the same support as the standard `RoutingContext`\nVert.x implementation it derives from using the regular `r.response().putHeader(name,value)` method,\nbut in addition - to augment the ability of deep business logic execution to quickly return results by\nthrowing an `HttpError` type - instead of sending a `Request` or `HttpServerResponse` instance deep\ninto business logic code, your business logic can attach the needed headers to the `HttpError` status\nobject they throw. \n\nFor example:\n\n```java\nthrow new Found().addHeader(\"Location\", \"https://example.com\");\n```\n\nBecause of the usefulness of such patterns, Irked supplies the helpers `Redirect` and `PermanentRedirect`\nthat implement adding `Location` headers to `Found` and `MovedPermanently` respectively, so the above\nexample is equivalent to:\n\n```java\nthrow new Redirect(\"https://example.com\");\n```\n\n#### Explicit handler ordering\n\nAs discussed above, while Irked supports both using fields to route requests to, as well as\nmethods, when annotating methods to handle incoming requests - due to the limitations\nof Java reflection, Irked cannot identify the correct order to setup the Vert.x-web\nroutes. You can use the Irked `@Order` annotation to specify an explicit order for\nhandler methods (Using the [Route.order() method](https://vertx.io/docs/vertx-web/java/#_route_order))\nso that Irked can guarantee that when you call `RoutingContext.next()`, the correct handler\nwill be called:\n\n```java\npackage com.example.api;\n\nimport tech.greenfield.vertx.irked.*;\nimport tech.greenfield.vertx.irked.status.*;\nimport tech.greenfield.vertx.irked.annotations.*;\n\nclass Example extends Controller {\n\n    @Order(1000) // make sure sending the response is the last thing that happens\n    public void helloWorld(Request r) {\n        r.sendContent(r.get(\"message\"), new OK());\n    }\n\n    public void thisComesFirst(Request r) {\n        r.put(\"message\",\"Hello World!\");\n        r.next();\n    }\n}\n```\n\n#### Subclassing Controllers\n\nMost examples in this documentation show how to implement a controller by\ndirectlying extending Irked `Controller` class, though Irked support using a class\nheirarchy by configuring handlers for both the implemenation class as well as any of \nits super classes.\n\nIf it makes sense for you use case to have controller base classes with their own \n\"default\" handlers, this would work as expected:\n\n```java\npackage com.example.api;\n\nimport tech.greenfield.vertx.irked.*;\nimport tech.greenfield.vertx.irked.status.*;\nimport tech.greenfield.vertx.irked.annotations.*;\n\nabstract class BaseExample extends Controller {\n\t@Get(\"/\")\n    WebHandler index = r -\u003e r.sendContent(\"Base index\", new OK());\n}\n\nclass SpecificExample extends BaseExample {\n\t@Get(\"/document\")\n    WebHandler document = r -\u003e r.sendContent(\"This document\", new OK());\n}\n```\n\nIn the above example, when mounting `SpecificExample` in a router, both `/document` \n(implemented in `SpecificExample.document`) as well as `/` (implemented in `BaseExample.index`) become visible.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FGreenfieldTech%2Firked","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FGreenfieldTech%2Firked","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FGreenfieldTech%2Firked/lists"}