Takes is a true object-oriented and immutable Java 8 web development framework. Its key benefits, compared to all others, include these four fundamental principles:
- Not a single
null(why NULL is bad) - Not a single
publicstaticmethod (why they're bad) - Not a single mutable class (why they're bad)
- Not a single
instanceofkeyword, type casting, or reflection (why)
Of course, there are no configuration files. Besides that, these are the more traditional features, out of the box:
- Hit-refresh debugging
- XML+XSLT
- JSON
- RESTful
- Templates, including Apache Velocity
The following is not and will not be supported:
Open-source systems that use Takes: rultor.com (sources), jare.io (sources).
Watch these videos to learn more: An Immutable Object-Oriented Web Framework and Takes, Java Web Framework, Intro. This blog post may help you as well.
- Quick Start
- Build and Run With Maven
- Build and Run With Gradle
- Unit Testing
- Integration Testing
- A Bigger Example
- Templates
- Static Resources
- Hit Refresh Debugging
- Request Methods (POST, PUT, HEAD, etc.)
- Request Parsing
- Form Processing
- Exception Handling
- Redirects
- RsJSON
- RsXembly
- GZIP Compression
- SSL Configuration
- Authentication
- Command Line Arguments
- Logging
- Directory Layout
- Optional dependencies
- Backward compatibility
- Version pattern for RESTful API
- Architecture
- How to contribute
Create this App.java file:
import org.takes.http.Exit;
import org.takes.http.FtBasic;
import org.takes.facets.fork.FkRegex;
import org.takes.facets.fork.TkFork;
public final class App {
public static void main(final String... args) throws Exception {
new FtBasic(
new TkFork(new FkRegex("/", "hello, world!")), 8080
).start(Exit.NEVER);
}
}Then, download takes-1.26.0-jar-with-dependencies.jar
and compile your Java code:
javac -cp takes-1.26.0-jar-with-dependencies.jar App.javaNow, run it like this:
java -Dfile.encoding=UTF-8 -cp takes-1.26.0-jar-with-dependencies.jar:. AppIt should work!
This code starts a new HTTP server on port 8080 and renders a plain-text page for all requests at the root URI.
Caution
Pay attention that UTF-8 encoding is set on the command line.
The entire framework relies on your default Java encoding, which is not
necessarily UTF-8 by default. To be sure, always set it on the command line
with file.encoding Java argument. We decided not to hard-code "UTF-8" in
our code mostly because this would be against the entire idea of
Java localization,
according to which a user always should have a choice of encoding and language
selection. We're using Charset.defaultCharset() everywhere in the code.
If you're using Maven, this is how your pom.xml should look:
<project>
<dependencies>
<dependency>
<groupId>org.takes</groupId>
<artifactId>takes</artifactId>
<version>1.26.0</version>
</dependency>
</dependencies>
<profiles>
<profile>
<id>hit-refresh</id>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<executions>
<execution>
<id>start-server</id>
<phase>pre-integration-test</phase>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>foo.App</mainClass> <!-- your main class -->
<cleanupDaemonThreads>false</cleanupDaemonThreads>
<arguments>
<argument>--port=${port}</argument>
</arguments>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>With this configuration you can run it from the command line:
mvn clean integration-test -Phit-refresh -Dport=8080Maven will start the server and you can see it at http://localhost:8080.
Create a Take with constructor accepting ServletContext:
package com.myapp;
public final class TkApp implements Take {
private final ServletContext ctx;
public TkApp(final ServletContext context) {
this.ctx = context;
}
@Override
public Response act(final Request req) throws Exception {
return new RsText("Hello servlet!");
}
}Add org.takes.servlet.SrvTake to your web.xml, don't forget to specify
take class as servlet init-param:
<servlet>
<servlet-name>takes</servlet-name>
<servlet-class>org.takes.servlet.SrvTake</servlet-class>
<init-param>
<param-name>take</param-name>
<param-value>com.myapp.TkApp</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>takes</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>If you're using Gradle, this is how your build.gradle should look:
plugins {
id 'java'
id 'application'
}
repositories {
mavenCentral()
}
dependencies {
implementation group: 'org.takes', name: 'takes', version: '1.24.6'
}
mainClassName='foo.App' //your main classWith this configuration you can run it from the command line:
gradle run -Phit-refresh -Dport=8080This is how you can unit-test the app, using JUnit 4.x and Hamcrest:
public final class AppTest {
@Test
public void returnsHttpResponse() throws Exception {
MatcherAssert.assertThat(
new RsPrint(
new App().act(new RqFake("GET", "/"))
).printBody(),
Matchers.equalTo("hello, world!")
);
}
}You can create a fake request with form parameters like this:
new RqForm.Fake(
new RqFake(),
"foo", "value-1",
"bar", "value-2"
)Here is how you can test the entire server via HTTP, using JUnit and jcabi-http for making HTTP requests:
public final class AppITCase {
@Test
public void returnsTextPageOnHttpRequest() throws Exception {
new FtRemote(new App()).exec(
new FtRemote.Script() {
@Override
public void exec(final URI home) throws IOException {
new JdkRequest(home)
.fetch()
.as(RestResponse.class)
.assertStatus(HttpURLConnection.HTTP_OK)
.assertBody(Matchers.equalTo("hello, world!"));
}
}
);
}
}More complex integration testing examples can be found in one of the open source projects that use Takes, for example: rultor.com.
Let's make it a bit more sophisticated:
public final class App {
public static void main(final String... args) {
new FtBasic(
new TkFork(
new FkRegex("/robots\\.txt", ""),
new FkRegex("/", new TkIndex())
),
8080
).start(Exit.NEVER);
}
}The FtBasic accepts new incoming sockets on port 8080,
parses them according to HTTP 1.1 specification and creates instances
of class Request. Then, it gives requests to the instance of TkFork
(tk stands for "take") and expects it to return an instance of Take back.
As you probably understood already, the first regular expression that matches
returns a take. TkIndex is our custom class,
let's see how it looks:
public final class TkIndex implements Take {
@Override
public Response act(final Request req) {
return new RsHtml("<html>Hello, world!</html>");
}
}It is immutable and must implement a single method act(), which returns
an instance of Response. So far so good, but this class does not have access
to the HTTP request. Here is how to solve this:
new TkFork(
new FkRegex(
"/file/(?<path>[^/]+)",
new TkRegex() {
@Override
public Response act(final RqRegex request) throws Exception {
final File file = new File(
request.matcher().group("path")
);
return new RsHTML(
FileUtils.readFileToString(file, Charsets.UTF_8)
);
}
}
)
)We are using TkRegex instead of Take, in order to work with
RqRegex instead of a more generic Request. RqRegex gives an instance
of Matcher used by FkRegex for pattern matching.
Here is a more complex and verbose example:
public final class App {
public static void main(final String... args) {
new FtBasic(
new TkFork(
new FkRegex("/robots.txt", ""),
new FkRegex("/", new TkIndex()),
new FkRegex(
"/xsl/.*",
new TkWithType(new TkClasspath(), "text/xsl")
),
new FkRegex("/account", new TkAccount(users)),
new FkRegex("/balance/(?<user>[a-z]+)", new TkBalance())
)
).start(Exit.NEVER);
}
}An essential part of the Bigger Example is the
Front interface.
It encapsulates server's back-end
and is used to start an instance, which will accept requests and return results.
FtBasic, which is a basic front, implements that interface - you have
seen its usage in the above-mentioned example.
There are other useful implementations of this interface:
- The FtRemote class allows you to provide a script that will be executed against a given front. You can see how it's used in integration tests.
- The FtCli class allows you to start your application with command-line arguments. More details in Command Line Arguments.
- The FtSecure class allows you to start your application with SSL. More details in SSL Configuration.
The Back interface is the back-end that is responsible for IO operations on TCP network level. There are various useful implementations of that interface:
- The BkBasic class is a basic
implementation of the
Backinterface. It is responsible for accepting the request fromSocket, converting the socket's input to the Request, dispatching it to the provided Take instance, getting the result and printing it to the socket's output until the request is fulfilled. - The BkParallel class is
a decorator of the
Backinterface, that is responsible for running the back-end in parallel threads. You can specify the number of threads or try to use the default number, which depends on available processors number in JVM. - The BkSafe class is a decorator
of the
Backinterface, that is responsible for running the back-end in a safe mode. That means that it will ignore exceptions thrown from the originalBack. - The BkTimeable class is a
decorator of the
Backinterface, that is responsible for running the back-end for a specified maximum lifetime in milliseconds. It is constantly checking if the thread with the originalbackexceeds the provided limit and if so - it interrupts the thread of thatback. - The BkWrap class is a convenient
wrap over the original
Backinstance. It just delegates theacceptto thatBackand might be useful if you want to add your own decorators of theBackinterface. This class is used inBkParallelandBkSafeas a parent class.
Now let's see how we can render something more complex than a plain text.
First, XML+XSLT is a recommended mechanism of HTML rendering.
Even though it may seem complex, give it a try; you will not regret it.
Here is how we render a simple XML page that is transformed
to HTML5 on the fly (more about RsXembly below):
public final class TkAccount implements Take {
private final Users users;
public TkAccount(final Users users) {
this.users = users;
}
@Override
public Response act(final Request req) {
final User user = this.users.find(new RqCookies(req).get("user"));
return new RsLogin(
new RsXslt(
new RsXembly(
new XeStylesheet("/xsl/account.xsl"),
new XeAppend("page", user)
)
),
user
);
}
}This is how that User class may look:
public final class User implements XeSource {
private final String name;
private final int balance;
@Override
public Iterable<Directive> toXembly() {
return new Directives().add("user")
.add("name").set(this.name).up()
.add("balance").set(Integer.toString(this.balance));
}
}Here is how RsLogin may look:
public final class RsLogin extends RsWrap {
public RsLogin(final Response response, final User user) {
super(
new RsWithCookie(
response, "user", user.toString()
)
);
}
}Let's say you want to use Velocity:
public final class TkHelloWorld implements Take {
@Override
public Response act(final Request req) {
return new RsVelocity(
"Hi, ${user.name}! You've got ${user.balance}",
new RsVelocity.Pair("user", new User())
);
}
}You will need this extra dependency in classpath:
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<scope>runtime</scope>
</dependency>For Gradle users:
dependencies {
...
runtime group: 'org.apache.velocity',
name: 'velocity-engine-core',
version: 'x.xx' // put the version here
...
}Very often you need to serve static resources to your web users, like CSS stylesheets, images, JavaScript files, etc. There are a few supplementary classes for that:
new TkFork(
new FkRegex("/css/.+", new TkWithType(new TkClasspath(), "text/css")),
new FkRegex("/data/.+", new TkFiles(new File("/usr/local/data")))
)The TkClasspath class takes the static part of the request URI and finds
a resource with this name in the classpath.
TkFiles looks for files by name in the configured directory.
TkWithType sets the content type of all responses coming out of
the decorated take.
It is a very convenient feature. Once you start the app you want to be able to modify its static resources (CSS, JS, XSL, etc), refresh the page in a browser and immediately see the result. You don't want to re-compile the entire project and restart it. Here is what you need to do to your sources in order to enable that feature:
new TkFork(
new FkRegex(
"/css/.+",
new TkWithType(
new TkFork(
new FkHitRefresh(
"./src/main/resources/foo/scss/**", // what sources to watch
"mvn sass:compile", // what to run when sources are modified
new TkFiles("./target/css")
),
new FkFixed(new TkClasspath())
),
"text/css"
)
)
)This FkHitRefresh fork is a decorator of Take. Once it sees
X-Take-Refresh header in the request, it realizes that the server
is running in
"hit-refresh" mode and passes the request to the encapsulated take. Before it
passes the request, it tries to understand whether any of the resources
are older than the compiled files. If they are older, it tries
to run the compilation tool to build them again.
Here is an example:
new TkFork(
new FkRegex(
"/user",
new TkFork(
new FkMethods("GET", new TkGetUser()),
new FkMethods("POST,PUT", new TkPostUser()),
new FkMethods("DELETE", new TkDeleteUser())
)
)
)Here is how you can parse an instance of Request:
Href href = new RqHref.Base(request).href();
URI uri = href.uri();
Iterable<String> values = href.param("key");For a more complex parsing try to use Apache HTTP Client or something similar.
Here is an example:
public final class TkSavePhoto implements Take {
@Override
public Response act(final Request req) {
final String name = new RqForm(req).param("name");
return new RsWithStatus(HttpURLConnection.HTTP_NO_CONTENT);
}
}By default, TkFork lets all exceptions bubble up. If one of your Takes
crashes, a user will see a default error page. Here is how you can configure
this behavior:
public final class App {
public static void main(final String... args) {
new FtBasic(
new TkFallback(
new TkFork(
new FkRegex("/robots\\.txt", ""),
new FkRegex("/", new TkIndex())
),
new FbChain(
new FbStatus(404, new RsText("Sorry, page is absent")),
new FbStatus(405, new RsText("This method is not allowed here")),
new Fallback() {
@Override
public Iterator<Response> route(final RqFallback req) {
return Collections.<Response>singleton(
new RsHTML("Oops, something went terribly wrong!")
).iterator();
}
}
)
),
8080
).start(Exit.NEVER);
}
}TkFallback decorates an instance of Take and catches all exceptions any of
its Takes may throw. Once it is thrown, an instance of FbChain will
find the most suitable fallback and will fetch a response from there.
Sometimes it's very useful to return a redirect response (30x status code),
either by a normal return or by throwing an exception. This example
illustrates both methods:
public final class TkPostMessage implements Take {
@Override
public Response act(final Request req) {
final String body = new RqPrint(req).printBody();
if (body.isEmpty()) {
throw new RsForward(
new RsFlash("Message can't be empty")
);
}
// save the message to the database
return new RsForward(
new RsFlash(
"Thanks, the message was posted"
),
"/"
);
}
}Then, you should decorate the entire TkFork with this
TkForward and TkFlash:
public final class App {
public static void main(final String... args) {
new FtBasic(
new TkFlash(
new TkForward(
new TkFork(new FkRegex("/", new TkPostMessage())
)
),
8080
).start(Exit.NEVER);
}
}Here is how we can deal with JSON:
public final class TkBalance extends TkFixed {
@Override
public Response act(final RqRegex request) {
return new RsJSON(
new User(request.matcher().group("user"))
);
}
}This is the method to add to User:
public final class User implements XeSource, RsJSON.Source {
@Override
public JsonObject toJSON() {
return Json.createObjectBuilder()
.add("balance", this.balance)
.build();
}
}Here is how to generate an XML page using Xembly:
Response response = new RsXembly(
new XeAppend("page"),
new XeDirectives("XPATH '/page'", this.user)
)This is a complete example, with all possible options:
Response response = new RsXembly(
new XeStylesheet("/xsl/account.xsl"), // add processing instruction
new XeAppend(
"page", // create a DOM document with "page" root element
new XeMillis(false), // add "millis" attribute to the root, with current time
user, // add user to the root element
new XeSource() {
@Override
public Iterable<Directive> toXembly() {
return new Directives().add("status").set("alive");
}
},
new XeMillis(true), // replace "millis" attribute with take building time
),
)This is the output that will be produced:
<?xml version='1.0'?>
<?xsl-stylesheet href='/xsl/account.xsl'?>
<page>
<millis>5648</millis>
<user>
<name>Jeff Lebowski</name>
<balance>123</balance>
</user>
<status>alive</status>
</page>To avoid duplication of all this scaffolding in every page, you can create your own class, which will be used in every page, for example:
Response response = new RsXembly(
new XeFoo(user)
)This is how this XeFoo class would look:
public final class XeFoo extends XeWrap {
public XeFoo(final String stylesheet, final XeSource... sources) {
super(
new XeAppend(
"page",
new XeMillis(false),
new XeStylesheet(stylesheet),
new XeChain(sources),
new XeSource() {
@Override
public Iterable<Directive> toXembly() {
return new Directives().add("status").set("alive");
}
},
new XeMillis(true)
)
);
}
}You will need this extra dependency in classpath:
<dependency>
<groupId>com.jcabi.incubator</groupId>
<artifactId>xembly</artifactId>
</dependency>More about this mechanism in this blog post: XML Data and XSL Views in Takes Framework.
Here is how we drop a cookie to the user:
public final class TkIndex implements Take {
@Override
public Response act(final Request req) {
return new RsWithCookie("auth", "John Doe");
}
}An HTTP response will contain this header, which will place
an auth cookie into the user's browser:
HTTP/1.1 200 OK
Set-Cookie: auth="John Doe"
This is how you read cookies from a request:
public final class TkIndex implements Take {
@Override
public Response act(final Request req) {
// the list may be empty
final Iterable<String> cookies = new RqCookies(req).cookie("my-cookie");
}
}If you want to compress all your responses with GZIP, wrap your take in
TkGzip:
new TkGzip(take)Now, each request that contains the Accept-Encoding request header with gzip
compression method inside will receive a GZIP-compressed response. Also,
you can compress an individual response, using RsGzip decorator.
Say you want to return different content based on the Accept header
of the request (a.k.a.
content negotiation):
public final class TkIndex implements Take {
@Override
public Response act(final Request req) {
return new RsFork(
req,
new FkTypes("text/*", new RsText("it's a text")),
new FkTypes("application/json", new RsJSON("{\"a\":1}")),
new FkTypes("image/png", /* something else */)
);
}
}First of all, set up your keystore settings, for example:
final String file = this.getClass().getResource("/org/takes/http/keystore").getFile();
final String password = "abc123";
System.setProperty("javax.net.ssl.keyStore", file);
System.setProperty("javax.net.ssl.keyStorePassword", password);
System.setProperty("javax.net.ssl.trustStore", file);
System.setProperty("javax.net.ssl.trustStorePassword", password);Then simply create an instance of the
FtSecure class with socket factory
final ServerSocket skt = SSLServerSocketFactory.getDefault().createServerSocket(0);
new FtRemote(
new FtSecure(new BkBasic(new TkFixed("hello, world")), skt),
skt,
true
);Here is an example of login via Facebook:
new TkAuth(
new TkFork(
new FkRegex("/", new TkHTML("Hello, check <a href='/acc'>account</a>")),
new FkRegex("/acc", new TkSecure(new TkAccount()))
),
new PsChain(
new PsCookie(
new CcSafe(new CcHex(new CcXOR(new CcCompact(), "secret-code")))
),
new PsByFlag(
new PsByFlag.Pair(
PsFacebook.class.getSimpleName(),
new PsFacebook("facebook-app-id", "facebook-secret")
),
new PsByFlag.Pair(
PsLogout.class.getSimpleName(),
new PsLogout()
)
)
)
)Then, you need to show a login link to the user, which the user can click to get to the Facebook OAuth authentication page. Here is how you do this with XeResponse:
new RsXembly(
new XeStylesheet("/xsl/index.xsl"),
new XeAppend(
"page",
new XeFacebookLink(req, "facebook-app-id"),
// ... other xembly sources
)
)The link will be added to the XML page like this:
<page>
<links>
<link rel="take:facebook" href="https://www.facebook.com/dialog/oauth..."/>
</links>
</page>Similar mechanism can be used for PsGithub,
PsGoogle, PsLinkedin, PsTwitter, etc.
This is how to get the currently logged-in user:
public final class TkAccount implements Take {
@Override
public Response act(final Request req) {
final Identity identity = new RqAuth(req).identity();
if (identity.equals(Identity.ANONYMOUS)) {
// returns "urn:facebook:1234567" for a user logged in via Facebook
identity.urn();
}
}
}More about it in this blog post: How Cookie-Based Authentication Works in the Takes Framework.
There is a convenient FtCLI class that parses command-line arguments and
starts the necessary Front accordingly.
There are a few command-line arguments that should be passed to
FtCLI constructor:
--port=1234 Tells the server to listen to TCP port 1234
--lifetime=5000 The server will die in five seconds (useful for i-testing)
--hit-refresh Run the server in hit-refresh mode
--daemon Runs the server in Java daemon thread (for i-testing)
--threads=30 Processes incoming HTTP requests in 30 parallel threads
--max-latency=5000 Maximum latency in milliseconds per each request
(longer requests will be interrupted)
For example:
public final class App {
public static void main(final String... args) {
new FtCLI(
new TkFork(new FkRegex("/", "hello, world!")),
args
).start(Exit.NEVER);
}
}Then run it like this:
java -cp take.jar App.class --port=8080 --hit-refreshYou should see "hello, world!" at http://localhost:8080.
Parameter --port also accepts a file name, instead of a number. If the file
exists, FtCLI will try to read its content and use it as
port number. If the file is absent, FtCLI will allocate a new random
port number, use it to start a server, and save it to the file.
The framework sends all logs to SLF4J logging facility. If you want to see them, configure one of SLF4J bindings.
To make a Take log, wrap it in the TkSlf4j, for example:
new TkSlf4j(
new TkFork(/* your code here */)
)You are free to use any build tool, but we recommend Maven. This is how your project directory layout may/should look:
src/
main/
java/
foo/
App.java
scss/
coffeescript/
resources/
vtl/
xsl/
js/
css/
robots.txt
pom.xml
LICENSE.txt
If you are using Maven and include Takes as a dependency in your own project,
you can choose which of the optional dependencies to include in your project.
The list of all of the optional dependencies can be seen in the
Takes project pom.xml.
For example, to use the Facebook API shown above, simply add a dependency to
the restfb API in your project:
<dependency>
<groupId>com.restfb</groupId>
<artifactId>restfb</artifactId>
<scope>runtime</scope>
</dependency>For Gradle, add the dependencies as usual:
dependencies {
...
runtime group: 'com.restfb', name: 'restfb', version: '1.15.0'
}Version 2.0 is not backward-compatible with previous versions.
The URL should NOT contain the version, but the type requested.
For example:
===>
GET /architect/256 HTTP/1.1
Accept: application/org.takes.architect-v1+xml
<===
HTTP/1.1 200 OK
Content-Type: application/org.takes.architect-v1+xml
<architect>
<name>Yegor Bugayenko</name>
</architect>
Then clients aware of a newer version of this service can call:
===>
GET /architect/256 HTTP/1.1
Accept: application/org.takes.architect-v2+xml
<===
HTTP/1.1 200 OK
Content-Type: application/org.takes.architect-v2+xml
<architect>
<firstName>Yegor</firstName>
<lastName>Bugayenko</lastName>
<salutation>Mr.</salutation>
</architect>
This article explains why it's done this way.
Everything is a composable interface, never a framework base class.
The entire framework rests on three interfaces:
Take (converts a request into a
response), Request, and
Response. Both Request and
Response extend Head (HTTP header
lines) and Body (a raw InputStream).
Every component you write or use implements one of these interfaces; there are
no abstract base classes to extend, no framework lifecycle callbacks to hook
into, and no annotations to scan. This differs sharply from Spring
MVC or
JAX-RS, where you subclass
framework types and let a container discover and wire your code at startup.
Behavior is added by wrapping, not by mutating.
The framework is built on the Decorator
pattern end-to-end.
TkWrap and
RsWrap are the base decorators for
takes and responses respectively. Cross-cutting concerns — logging
(TkSlf4j), GZIP compression
(TkGzip), authentication
(TkAuth), error fallback
(TkFallback) — are
each a thin wrapper around any other Take. You compose them in your main()
method instead of registering them as AOP
advice
or Servlet filters, which means
the composition is visible, linear, and verifiable without running the
application.
The server is split into a Front and a Back, each independently composable.
Front owns the TCP socket lifecycle
(accept connections, honour Exit), while
Back owns the HTTP lifecycle for a
single socket (parse request, call Take, write response).
BkBasic provides sequential
request handling; BkParallel
wraps it with a thread pool;
BkSafe suppresses exceptions;
BkTimeable enforces a
per-request timeout. Each layer is a decorator of the same Back interface, so
you can combine them independently without touching routing or business logic.
Traditional embedded containers like Jetty or
Tomcat bundle all of this into one opaque object.
Routing is a chain of explicit Fork predicates, not a registry of annotated
methods.
TkFork iterates over
Fork instances and returns
the first that matches. Built-in forks match on URI path regex
(FkRegex), HTTP method
(FkMethods), query
parameters, or Accept header. Because a Fork is just an interface returning
Opt<Response>, you can write your own in a few lines. There is no reflection,
no annotation scanning, and no route-to-handler registration table, unlike
Spring MVC
@RequestMapping
or Jersey @Path. The constraint this
satisfies is that routing must be statically visible in the code.
No null anywhere: the framework uses its own Opt<T> type.
Opt<T> has exactly two
implementations — Opt.Single<T> (value present) and Opt.Empty<T> (value
absent) — and calling .get() on an empty Opt throws immediately rather than
producing a NullPointerException far away. This is enforced across the entire
codebase: authentication, routing, and cookie parsing all return Opt<T> rather
than nullable references. The constraint is the same as that of Haskell's
Maybe or Java 8's
Optional,
but predates widespread Optional adoption in the Java ecosystem and avoids the
footgun of Optional.get() on an empty optional.
All objects are immutable and thread-safe; there are no setters or mutable
fields.
Every Take, Request, and Response implementation stores state only in
final fields set by the constructor. Concurrency is achieved by creating new
wrapper objects rather than updating shared state, which means the same Take
instance can safely handle thousands of simultaneous requests without
synchronisation. This contrasts with
Servlet's HttpServlet, where
service() is called on one shared mutable instance, and with Spring
beans
that default to singleton scope and require careful threading discipline.
No configuration files; the object graph is assembled in plain Java.
There are no XML descriptors, no .properties files, no YAML, and no
annotations that drive startup scanning. The entire application is a nested
constructor call — a Front wrapping a Back wrapping a Take — assembled in
a single main() method. The constraint behind this is that configuration
should be verifiable by the compiler and refactorable by an IDE. This approach
echoes the philosophy of Elegant Objects and
is the opposite of Spring Boot
autoconfiguration
or Dropwizard YAML
bundles.
The recommended view layer is XML produced in Java and transformed to HTML by
XSLT.
Takes provides RsXembly to
build an XML document from composable
XeSource objects (using
Xembly directives), and
RsXslt to transform that XML to HTML
at response time using a classpath stylesheet. This separates structure (Java +
XML) from presentation (XSL), and keeps transformation logic out of Java
entirely. It is an alternative to template engines such as Apache
Velocity (also supported via
RsVelocity) or
Thymeleaf, which mix control logic with markup.
Authentication is a pair of enter/exit methods on a Pass interface,
composable like any other object.
Pass has two methods:
enter(Request) returns the authenticated Identity (or Opt.Empty), and
exit(Response, Identity) attaches session information (e.g. a cookie) to the
outgoing response. Providers — OAuth via Facebook, GitHub, Google, or
cookie-based — all implement Pass and can be chained with
PsChain or combined with
PsAll. This differs from
Spring Security's Filter-based
pipeline, where authentication and session management are configured through a
global SecurityFilterChain rather than composed locally at the call site.
A Servlet bridge enables deployment on existing JEE containers without
changing application code.
SrvTake adapts any Take to a
standard HttpServlet, allowing
the same application to run standalone (via FtBasic) or inside Tomcat, Jetty,
or any Servlet 3.x container.
The adapter translates a ServletRequest into a Request and serialises the
Response back to the ServletResponse. This provides deployment flexibility
without coupling the application logic to any container API.
Fork the repository, make changes, and send us a pull request. We will review
your changes and apply them to the master branch shortly, provided
they don't violate our quality standards. To avoid frustration, before
sending us your pull request, please run the full Maven build:
mvn clean install -PquliceTo avoid build errors, use Maven 3.2+.
Note that our pom.xml inherits a lot of configuration
from jcabi-parent.
This article
explains why it's done this way.