Research
Security News
Malicious npm Package Targets Solana Developers and Hijacks Funds
A malicious npm package targets Solana developers, rerouting funds in 2% of transactions to a hardcoded address.
com.mastfrog:acteur-header-entities
Advanced tools
Simple types, parsers and builders for common HTTP header values such as cache-control, basic auth credentials, frame options, strict transport security and connection that are easy to work with programmatically, parse out of inbound headers and generate correctly into outbound headers.
Acteur is a framework for writing web server applications with
Netty by composing together reusable chunks of logic called
Acteur
s (think of the Actor pattern, but a little bit foreign :-)).
A further description of the framework's aims can be found in this blog. This project uses Netty's 4.x (with its revised API) for HTTP.
Read the acteur tutorial for a step-by-step description of building a web API.
Or read the FAQ.
It's goal is to make it easy to write small, fast, scalable HTTP servers while ending up with reusable code as a natural side effect of using the framework.
If you think this project is worthwhile, or there are features you'd like to see, consider donating.
It uses a few best-of-breed libraries in addition to Netty to work its magic
(such as Joda Time for dates and times, Jackson for JSON processing and
Guava or MIME types and a few other things).
Most importantly, it uses Google's Guice dependency injection framework to
decouple those reusable chunks of logic.
An Acteur application is a subclass of Application
. In a pattern
we will see again, most of the code and work of an Application
happens in its
constructor, and most of it consists of adding Page
subtypes (Class
objects)
to the application.
Essentially we are treating constructors as function objects. Each Acteur construtor
outputs a State
which can contain additional objects that the next Acteur in
the chain can request by mentioning them in their constructor arguments.
An Application is a list of Pages, and each Page is a list of Acteurs. Typically these are all specified by passing Class objects, and instantiated on-demand, once per request.
Similarly, most of the work of a Page
also happens in its constructor, in the
form of adding Acteur subclasses (instantiated, or just the types) to the Page.
At runtime, the Page
instance can be used to set the headers of
the response. Actual Page and Acteur instances are constructed on a per-request
basis.
There are a few things you can override if you want to, but the constructor is where the action is.
This breaking of logic into small classes instantiated by Guice accomplishes several things:
This ends up adding up to something resembling recursive callbacks, without the mess. Whereas in Javascript one might write
blogs.find(id, function(err, blog) {
blog.withContent(function(err, content) {
response.write(content);
});
});
with the caveat that in fact, not of the nested functions run sequentially, in Acteur, one would write one Acteur to locate the blog and inject it into the next, and so forth - small, readable, reusable chunks of logic run just as asynchronously. Incidentally, since the objects which need to be in-scope are (literally) contained in the scope, the required memory footprint can be smaller (only variables that will actually be used are included, rather than every variable visible from the current call).
Acteur is in use in a few production web applications in-house - which is to say it is somebody's job to work on and improve it. Recently Netty has undergone major incompatible refactoring. This project will keep up with such things, but occasionally changes in Netty may make it uncompilable until Acteur can be updated. Hopefully the Netty folks will finish their refactoring soon.
This framework emerged from a bunch of heterogenous things:
Acteur takes a page from Apache Wicket in terms of being built around Page and Application classes which you actually subclass (as opposed to typical Java web applications with scads of XML and factories for factories). It is explicitly not trying to be a declarative framework - someone could trivially create single Page or Application classes that read declarative data and populate themselves from that. Rather Acteur is the bones you would build such a thing on top of.
Netty supports all sorts of protocols, not just HTTP. Acteur is specifically solving the problem of HTTP, at least for now (SPDY is a glimmer in its author's eye).
Acteur is not in the business of dictating to you how you model data - in fact,
it is focused more on the steps that happen before you get around to producing
a response body (Netty's ChannelFutureListener
s work quite well for that) - i.e.
doing handling cache-related headers simply, well and up-front rather than as
an afterthought. And making sure that any logic this will be shared by multiple
HTTP calls can be implemented cleanly as separate pieces.
So, Acteur does not hide Netty's API for actually writing data. Very simple
HTTP responses can be composed by passing a string to the constructor of the
RespondWith
state; for doing more complicated output processing, you probably
want to implement Netty's ChannelFutureListener
and write directly to the
output channel.
There are a lot of well-done solutions for generating HTML or JSON, doing templating and that sort of thing.
As mentioned above, an application is composed from Pages, and a Page is composed from Acteurs. Here is what that looks like:
public class App extends Application {
App() {
add(HelloPage.class);
}
static class HelloPage extends Page {
@Inject
HelloPage(ActeurFactory factory) {
add(factory.matchMethods(Method.GET));
add(factory.matchPath("^hello$")); // A regular expression
add(SayHelloActeur.class);
}
}
static class SayHelloActeur extends Acteur {
@Inject
SayHelloActeur(Page page, Event evt) {
page.getReponseHeaders().setContentType("text/html; charset=utf8");
setState(new RespondWith(HttpResponseStatus.OK,
"<html><head><title>Hello</title></head><body><h1>Hello World</h1>"
+ "Hello from Acteur</body></html>"));
}
}
public static void main(String[] args) throws IOException, InterruptedException {
ServerModule<App> module = new ServerModule<>(App.class);
module.start(8192);
}
}
Now, say we would like to have this application look for a url parameter name
and generate some customized output. That lets us show off what you can do with
injection.
We'll add one line to the the application (outermost) class itself, to tell Guice
that java.lang.String
is a type which may be injected from one Acteur into another
(in practice, String is an odd choice, but it works for a demo):
@ImplicitBindings ( String.class )
The framework lets Acteurs dynamically create objects for injection into subsequent Acteurs. Guice demands that all type-bindings be configured at application start-time. So in order to have a raw String be allowed by Guice, we need to tell Guice that String is one of the classes it should bind.
Then we modify our Acteurs and page slightly.
static class HelloPage extends Page {
@Inject
HelloPage(ActeurFactory factory) {
add(factory.matchMethods(Method.GET));
add(factory.matchPath("^hello$"));
add(factory.requireParameters("name"));
add(FindNameActeur.class);
add(SayHelloActeur.class);
}
}
static class FindNameActeur extends Acteur {
@Inject
FindNameActeur(Event evt) {
// name will always be non-null - requireParameters() will have
// aborted the request with a 400 BAD REQUEST response before we
// get here if it is missing
String name = evt.getParameter("name");
// name will be injected into SayHelloActeur's constructor
setState(new ConsumedLockedState(name));
}
}
static class SayHelloActeur extends Acteur {
@Inject
SayHelloActeur(Page page, String name) {
page.getReponseHeaders().setContentType("text/html; charset=utf8");
setState(new RespondWith(HttpResponseStatus.OK,
"<html><head><title>Hello</title></head><body><h1>Hello " + name + "</h1>"
+ "Hello from Acteur to " + name + "</body></html>"));
}
}
So if you run this application and run, say
curl -i http://localhost:8192/hello?name=Tim
you will get a nice personalized hello page. Admittedly this example is a bit
contrived; for more real-world uses, see the two ActeurFactory
methods:
injectRequestBodyAsJSON(Class<T> type)
- this uses Jackson to parse the
request body into the type requested (if an error occurs, you can handle it by
overriding Application.onError
)injectRequestParametersAs
- allows you to provide an Java interface which
should be used to represent the request parameters; literally the method names
should match parameter names; supported types are Java primitive types,
DateTime and Duration. So you can simply add the Acteur produced by
calling acteurFactory.injectRequestParametersAs(MyType.class)
and then
write an Acteur that requests a MyType
in its constructor.The above example simply uses a Java String
to send all of its output at
once. If you want to do something more complex, you will simply use Netty's clean and
simple API for writing output to a channel. Instead of passing the string to
the RespondWith
constructor, leave it out. Say you want to pipeline a bunch
of output which may take some time to compute:
static class MyOutputter implements ChannelFutureListener {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
future = future.channel().write(Unpooled.wrappedBuffer("The output".getBytes()));
// add this as a listener to write more output, or add
// ChannelFutureListener.CLOSE
}
}
Acteur uses the Giulius library
to facilitate binding values in properties
files to @Named
values which Guice can inject. While not going into exhaustive
detail here, what this does is it makes it easy to configure an application
using a hierarchy of properties files. The default file name is defaults.properties
(but you can use something different by specifying it at startup time). In
a nutshell, how it works is this:
META-INF/settings/generated-defaults.properties
- these are generated using
the @Defaults
annotationMETA-INF/settings/defaults.properties
/etc/defaults.properties
~/defaults.properties
(a file in the process` user's home dir)./defaults.properties
(a file in the process working dir)To set base values for things, the easy (and reliable) way is to use the
@Defaults
annotation - this guarantees that configuration files are optional
and there are always sane default values. @Defaults
simply takes an array
of strings, and uses properties-file syntax, e.g.
@Defaults({"port=8192","dbserver=localhost"})
This could also be written
@Defaults("port=8192\ndbserver=localhost")
but the former is more readable.
A lot of the heavy lifting in creating Acteurs which are injected by objects from
other Acteurs is handled by the utility class ReentrantScope
. Each
Acteur is instantiated within this scope (available from a getter on the Application).
The scope is entered multiple times, each time contributing any objects provided
by the previous Acteur in the chain. It is also possible to inject an
ExecutorService
which will wrap any Runnables or Callables posted to it in the
current scope contents, so it is possible to run code on a background thread
with the same scope contents as when it was posted (and in fact, this is how
Acteurs are run).
An Acteur must set its state, either by calling setState()
within
its constructor, or overriding getState()
. Three State subclasses
are provided as inner classes of the superclass (so they can only be instantiated
inside an Acteur subclass):
RejectedState
- the Acteur says it doesn't know what to do about the eventConsumedState
- the Acteur does recognize the request, and perhaps is providing
some objects for the next Acteur to useConsumedLockedState
- the Acteur recognizes the request to the degree that it
accepts responsibility for responding - no other Pages will be tried if subsequent
Acteurs reject the requestRespondWith
- processing of the event is complete, and the response should
be sentFAQs
Simple types, parsers and builders for common HTTP header values such as cache-control, basic auth credentials, frame options, strict transport security and connection that are easy to work with programmatically, parse out of inbound headers and generate correctly into outbound headers.
We found that com.mastfrog:acteur-header-entities demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
A malicious npm package targets Solana developers, rerouting funds in 2% of transactions to a hardcoded address.
Security News
Research
Socket researchers have discovered malicious npm packages targeting crypto developers, stealing credentials and wallet data using spyware delivered through typosquats of popular cryptographic libraries.
Security News
Socket's package search now displays weekly downloads for npm packages, helping developers quickly assess popularity and make more informed decisions.