{"id":15014369,"url":"https://github.com/timboudreau/acteur","last_synced_at":"2025-04-12T08:00:24.638Z","repository":{"id":7902631,"uuid":"9283641","full_name":"timboudreau/acteur","owner":"timboudreau","description":"A framework for writing lightweight, scalable servers with Guice and Netty","archived":false,"fork":false,"pushed_at":"2023-10-18T18:21:46.000Z","size":3349,"stargazers_count":69,"open_issues_count":1,"forks_count":9,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-03-26T03:11:18.204Z","etag":null,"topics":["acteur","framework","guice","http","java","netty","server"],"latest_commit_sha":null,"homepage":"","language":"Java","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/timboudreau.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,"governance":null}},"created_at":"2013-04-07T21:33:58.000Z","updated_at":"2024-10-10T07:28:32.000Z","dependencies_parsed_at":"2023-10-20T23:29:26.059Z","dependency_job_id":null,"html_url":"https://github.com/timboudreau/acteur","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/timboudreau%2Facteur","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/timboudreau%2Facteur/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/timboudreau%2Facteur/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/timboudreau%2Facteur/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/timboudreau","download_url":"https://codeload.github.com/timboudreau/acteur/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248537098,"owners_count":21120705,"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":["acteur","framework","guice","http","java","netty","server"],"created_at":"2024-09-24T19:45:31.704Z","updated_at":"2025-04-12T08:00:24.555Z","avatar_url":"https://github.com/timboudreau.png","language":"Java","funding_links":[],"categories":["网络编程"],"sub_categories":[],"readme":"Acteur\n======\n\nActeur is a framework for writing web server applications with \n[Netty](http://netty.io) by composing together reusable chunks of logic called\n\u003ccode\u003eActeur\u003c/code\u003es (think of the Actor pattern, but a little bit foreign :-)).\n\nA further description of the framework's aims can be found in\n[this blog](http://timboudreau.com/blog/Acteur/read).  This project uses \n[Netty's](http://netty.io) 4.x (with its revised API) for HTTP.\n\nRead the [acteur tutorial](../../../acteur-tutorial) for a step-by-step\ndescription of building a web API.\n\nOr read the [FAQ](blob/master/FAQ.md).\n\nIt's goal is to make it easy to write small, fast, scalable HTTP servers\nwhile ending up with reusable code as a natural side effect of using\nthe framework.\n\nIf you think this project is worthwhile, or there are features you'd like\nto see, consider [donating](http://timboudreau.com/donate.html).\n\nIt uses a few best-of-breed libraries in addition to Netty to work its magic\n(such as Joda Time for dates and times, Jackson for JSON processing and\nGuava or MIME types and a few other things).  \nMost importantly, it uses Google's Guice dependency injection framework to \ndecouple those reusable chunks of logic.\n\nAn Acteur application is a subclass of `Application`.  In a pattern\nwe will see again, most of the code and work of an `Application` happens in its\nconstructor, and most of it consists of adding `Page` subtypes (`Class` objects)\nto the application.\n\nEssentially we are treating constructors as function objects.  Each Acteur construtor\noutputs a `State` which can contain additional objects that the next Acteur in\nthe chain can request by mentioning them in their constructor arguments.\n\nAn Application is a list of Pages, and each Page is a list of Acteurs.  Typically\nthese are all specified by passing Class objects, and instantiated on-demand, once\nper request.\n\nSimilarly, most of the work of a `Page` also happens in its constructor, in the\nform of adding Acteur subclasses (instantiated, or just the types) to the Page.\nAt runtime, the `Page` instance can be used to set the headers of\nthe response.  Actual Page and Acteur instances are constructed on a per-request \nbasis.\n\nThere are a few things you can override if you want to, but the constructor is\nwhere the action is.\n\nThis breaking of logic into small classes instantiated by Guice accomplishes\nseveral things:\n\n * The natural pattern is logic reuse, not copy/paste programming\n * By composing the act of responding to requests into small wads of \nreusable logic, the system can take care of threading issues and interleave\nresponses to many requests, making it easy to write asynchronous, highly\nscalable code from chunks of single-threaded, imperative code - so fewer threads\ncan handle more requests\n * Individual pieces of logic form natural units for tests\n * Encouraging the use of constructors naturally leads to using final fields.\nMany bugs in all types of applications come from having mutable state, or having \nstate diffused throughout a lot of objects. So, unlike a lot of JavaBean-based\nframeworks, the natural way to do things in Acteur also happens\nto be a way that maximizes the help you can get from the compiler to ensure\nyour code is correct.\n\nThis ends up adding up to something resembling recursive callbacks, without the\nmess.  Whereas in Javascript one might write\n\n```java\nblogs.find(id, function(err, blog) {\n    blog.withContent(function(err, content) {\n       response.write(content);\n    });\n});\n```\n\nwith the caveat that in fact, not of the nested functions run sequentially,\nin Acteur, one would write one Acteur to locate the blog and inject it into\nthe next, and so forth - small, readable, reusable chunks of logic run just\nas asynchronously.  Incidentally, since the objects which need to be in-scope\nare (literally) contained in the scope, the required memory footprint can be\nsmaller (only variables that will actually be used are included, rather than\nevery variable visible from the current call).\n\n\nStatus\n------\n\nActeur *is* in use in a few production web applications in-house - which is\nto say it _is_ somebody's _job_ to work on and improve it. \nRecently Netty has undergone major incompatible refactoring.  This \nproject will keep up with\nsuch things, but occasionally changes in Netty may make it uncompilable until\nActeur can be updated.  Hopefully the Netty folks will finish their refactoring\nsoon.\n\n\nInspiration\n-----------\n\nThis framework emerged from a bunch of heterogenous things:\n\n * A desire to use Netty, and finding it was very low-level\n * Experience with asynchronous programming using [Node.js](http://nodejs.org) and\n    wanting to get some of the goodness of its programming model for Java\n * The knowledge that Servlet-based frameworks are not going to play nicely with\n    async servers any time soon because to be useful, the whole stack (file and\n    database I/O, you name it) needs to be non-blocking or a single-threaded server\n    is worse than useless\n * A suspicion that (unlikely as it sounds) NetBeans Visual Library's model of action response \n    chains would actually be a fertile model for the switching required for\n    responding to events from an evented server\n * Apache Wicket's lack of fear of constructors and doing work in constructors,\n    as compared to the factory fetish of most Java frameworks\n * The observation that if you want Guice to run some code for you, the best bet\n    is to put it in a constructor (setter injection is evil anyway)\n * Lots of experience with Guice, and the need for a way to create objects that\n    can supply objects to inject into other objects\n\n\nWhat Acteur Is\n--------------\n\nActeur takes a page from Apache Wicket in terms of being built around Page\nand Application classes which you actually subclass (as opposed to typical\nJava web applications with scads of XML and factories for factories).  It is\nexplicitly not trying to be a declarative framework - someone *could trivially*\ncreate single Page or Application classes that read declarative data and\npopulate themselves from that.  Rather Acteur is the bones you would build \nsuch a thing on top of.\n\nNetty supports all sorts of protocols, not just HTTP.  Acteur is specifically\nsolving the problem of HTTP, at least for now (SPDY is a glimmer in its author's\neye).\n\nActeur is not in the business of dictating to you how you model data - in fact,\nit is focused more on the steps that happen _before_ you get around to producing\na response body (Netty's `ChannelFutureListener`s work quite well for that) - i.e.\ndoing handling cache-related headers simply, well and up-front rather than as\nan afterthought.  And making sure that any logic this will be shared by multiple\nHTTP calls can be implemented cleanly as separate pieces.\n\nSo, Acteur does not hide Netty's API for actually writing data.  Very simple\nHTTP responses can be composed by passing a string to the constructor of the\n`RespondWith` state;  for doing more complicated output processing, you probably\nwant to implement Netty's `ChannelFutureListener` and write directly to the\noutput channel.\n\nThere are a lot of well-done solutions for generating HTML or\nJSON, doing templating and that sort of thing.\n\n\nA Basic Application\n-------------------\n\nAs mentioned above, an application is composed from Pages, and a Page is \ncomposed from Acteurs.  Here is what that looks like:\n\n```java\npublic class App extends Application {\n\n    App() {\n        add(HelloPage.class);\n    }\n\n    static class HelloPage extends Page {\n\n        @Inject\n        HelloPage(ActeurFactory factory) {\n            add(factory.matchMethods(Method.GET));\n            add(factory.matchPath(\"^hello$\")); // A regular expression\n            add(SayHelloActeur.class);\n        }\n    }\n\n    static class SayHelloActeur extends Acteur {\n\n        @Inject\n        SayHelloActeur(Page page, Event evt) {\n            page.getReponseHeaders().setContentType(\"text/html; charset=utf8\");\n            setState(new RespondWith(HttpResponseStatus.OK,\n                    \"\u003chtml\u003e\u003chead\u003e\u003ctitle\u003eHello\u003c/title\u003e\u003c/head\u003e\u003cbody\u003e\u003ch1\u003eHello World\u003c/h1\u003e\"\n                    + \"Hello from Acteur\u003c/body\u003e\u003c/html\u003e\"));\n        }\n    }\n\n    public static void main(String[] args) throws IOException, InterruptedException {\n        ServerModule\u003cApp\u003e module = new ServerModule\u003c\u003e(App.class);\n        module.start(8192);\n    }\n}\n```\n\nNow, say we would like to have this application look for a url parameter `name`\nand generate some customized output.  That lets us show off what you can do with\ninjection.\n\nWe'll add one line to the the application (outermost) class itself, to tell Guice\nthat `java.lang.String` is a type which may be injected from one Acteur into another\n(in practice, String is an odd choice, but it works for a demo):\n\n```java\n@ImplicitBindings ( String.class )\n```\n\nThe framework lets Acteurs dynamically create objects for injection into subsequent\nActeurs.  Guice demands that all type-bindings be configured at application\nstart-time.  So in order to have a raw String be allowed by Guice, we need to\ntell Guice that String is one of the classes it should bind.\n\nThen we modify our Acteurs and page slightly.\n\n```java\nstatic class HelloPage extends Page {\n    @Inject\n    HelloPage(ActeurFactory factory) {\n        add(factory.matchMethods(Method.GET));\n        add(factory.matchPath(\"^hello$\"));\n        add(factory.requireParameters(\"name\"));\n        add(FindNameActeur.class);\n        add(SayHelloActeur.class);\n    }\n}\n    \nstatic class FindNameActeur extends Acteur {\n    @Inject\n    FindNameActeur(Event evt) {\n        // name will always be non-null - requireParameters() will have\n        // aborted the request with a 400 BAD REQUEST response before we\n        // get here if it is missing\n        String name = evt.getParameter(\"name\");\n        // name will be injected into SayHelloActeur's constructor\n        setState(new ConsumedLockedState(name));\n    }\n}\n\nstatic class SayHelloActeur extends Acteur {\n    @Inject\n    SayHelloActeur(Page page, String name) {\n        page.getReponseHeaders().setContentType(\"text/html; charset=utf8\");\n        setState(new RespondWith(HttpResponseStatus.OK,\n                \"\u003chtml\u003e\u003chead\u003e\u003ctitle\u003eHello\u003c/title\u003e\u003c/head\u003e\u003cbody\u003e\u003ch1\u003eHello \" + name + \"\u003c/h1\u003e\"\n                + \"Hello from Acteur to \" + name + \"\u003c/body\u003e\u003c/html\u003e\"));\n    }\n}\n```\n\nSo if you run this application and run, say\n\n    curl -i http://localhost:8192/hello?name=Tim\n\nyou will get a nice personalized hello page.  Admittedly this example is a bit\ncontrived;  for more real-world uses, see the two `ActeurFactory` methods:\n\n * `injectRequestBodyAsJSON(Class\u003cT\u003e type)` - this uses Jackson to parse the \nrequest body into the type requested (if an error occurs, you can handle it by\noverriding `Application.onError`)\n * `injectRequestParametersAs` - allows you to provide an Java interface which\nshould be used to represent the request parameters;  *literally* the method names\nshould match parameter names;  supported types are Java primitive types,\nDateTime and Duration.  So you can simply add the Acteur produced by \ncalling `acteurFactory.injectRequestParametersAs(MyType.class)` and then\nwrite an Acteur that requests a `MyType` in its constructor.\n\n\nMore Complex Output\n-------------------\n\nThe above example simply uses a Java `String` to send all of its output at\nonce.  If you want to do something more complex, you will simply use Netty's clean and\nsimple API for writing output to a channel.  Instead of passing the string to\nthe `RespondWith` constructor, leave it out.  Say you want to pipeline a bunch\nof output which may take some time to compute:\n\n```java\nstatic class MyOutputter implements ChannelFutureListener {\n    @Override\n    public void operationComplete(ChannelFuture future) throws Exception {\n        future = future.channel().write(Unpooled.wrappedBuffer(\"The output\".getBytes()));\n        // add this as a listener to write more output, or add\n        // ChannelFutureListener.CLOSE\n    }\n}\n```\n\n\nConfiguration\n-------------\n\nActeur uses the [Giulius](https://github.com/timboudreau/giulius) library \nto facilitate binding values in properties\nfiles to `@Named` values which Guice can inject.  While not going into exhaustive\ndetail here, what this does is it makes it easy to configure an application\nusing a hierarchy of properties files.  The default file name is `defaults.properties`\n(but you can use something different by specifying it at startup time).  In \na nutshell, how it works is this:\n\n * At startup time, the system will look for and merge together properties\nfiles from the following locations, in this order (so values in subsequent files\noverride earlier ones):\n    * All files in JARs (classpath order) named \n`META-INF/settings/generated-defaults.properties` - these are generated using\nthe `@Defaults` annotation\n    * All files in JARs (classpath order) named `META-INF/settings/defaults.properties`\n    * `/etc/defaults.properties`\n    * `~/defaults.properties` (a file in the process` user's home dir)\n    * `./defaults.properties` (a file in the process working dir)\n\nTo set base values for things, the easy (and reliable) way is to use the\n`@Defaults` annotation - this guarantees that configuration files are optional\nand there are always sane default values.  `@Defaults` simply takes an array\nof strings, and uses properties-file syntax, e.g.\n\n```java\n@Defaults({\"port=8192\",\"dbserver=localhost\"})\n```\n\nThis could also be written\n\n```java\n@Defaults(\"port=8192\\ndbserver=localhost\")\n```\n\nbut the former is more readable.\n\nTechnical Details\n-----------------\n\nA lot of the heavy lifting in creating Acteurs which are injected by objects from\nother Acteurs is handled by the utility class `ReentrantScope`.  Each\nActeur is instantiated within this scope (available from a getter on the Application).\nThe scope is entered multiple times, each time contributing any objects provided\nby the previous Acteur in the chain.  It is also possible to inject an \n`ExecutorService` which will wrap any Runnables or Callables posted to it in the\ncurrent scope contents, so it is possible to run code on a background thread \nwith the same scope contents as when it was posted (and in fact, this is how\nActeurs are run).\n\nAn Acteur *must* set its state, either by calling `setState()` within\nits constructor, or overriding `getState()`.  Three State subclasses\nare provided as inner classes of the superclass (so they can only be instantiated\ninside an Acteur subclass):\n\n * `RejectedState` - the Acteur says it doesn't know what to do about the event\n * `ConsumedState` - the Acteur does recognize the request, and perhaps is providing\nsome objects for the next Acteur to use\n * `ConsumedLockedState` - the Acteur recognizes the request to the degree that it\naccepts responsibility for responding - no other Pages will be tried if subsequent\nActeurs reject the request\n * `RespondWith` - processing of the event is complete, and the response should\nbe sent\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftimboudreau%2Facteur","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftimboudreau%2Facteur","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftimboudreau%2Facteur/lists"}