templates.demos.helloWorld.super.html Maven / Gradle / Ivy
Show all versions of spincast-website Show documentation
{% extends "../helloWorld.html" %}
{% block demoSectionClasses %}demo_hello_world{% endblock %}
{% block meta_title %}Demo - Hello World - Supercalifragilisticexpialidocious{% endblock %}
{% block meta_description %}"Hello World!" demo and tutorial using Spincast - Supercalifragilisticexpialidocious version{% endblock %}
{% set demoId = "super" %}
{% block scripts %}
{% endblock %}
{% block demoBody %}
3. Supercalifragilisticexpialidocious version
Warning!
If you are new to Spincast, you may want to start with the Quick version
or the Better version instead of this one. We often expect a
"Hello World!" application to be very simple, and this is not what this
version is about.
In this tutorial, we will develop a "Hello World!" application using more advanced Spincast functionalities. We will :
-
Add an extra plugin.
-
Use a custom
Request Context
type.
-
Add an
add-on
to this
custom Request Context
type.
-
Use
externalized configurations
to change the port the Server will
be started on.
-
Create an endpoint that receives a username and returns
the HTML of the Github page associated with this username!
As you can see in the Quick version tutorial,
it is really easy to start a Spincast application if you simply need the default functionalities.
But you can go far beyond that...
Spincast is made from the ground up to be extensible, flexible. All the parts can be swapped or be extended since
dependency injection is used everywhere and there are no private methods.
Spincast artifact
First, let's add the org.spincast:spincast-default
Maven artifact to our pom.xml
(or build.gradle
) so we start with the
default plugins. It is interesting to know that to have total control,
we could also start with
the org.spincast:spincast-core
artifact instead and pick, one by one,
which plugins to use. But, most of the time, you'll start
with the default artifact :
<dependency>
<groupId>org.spincast</groupId>
<artifactId>spincast-default</artifactId>
<version>{{spincast.spincastCurrrentVersion}}</version>
</dependency>
Adding a plugin
In this application, we're going to use a plugin which is not
installed by default : Spincast HTTP Client.
This plugin provides an easy way to make HTTP requests.
Adding a plugin is, in general, a two steps process.
1. First, we add its Maven artifact to our pom.xml
(or build.gradle
) :
<dependency>
<groupId>org.spincast</groupId>
<artifactId>spincast-plugins-http-client</artifactId>
<version>{{spincast.spincastCurrrentVersion}}</version>
</dependency>
2. Then, since most plugins need to add or modify some bindings in the Guice context of an application,
we have to register them. This is done using the "plugin(...)"
method of the
Bootstrapper :
public static void main(String[] args) {
Spincast.configure()
.plugin(new SpincastHttpClientPlugin())
//...
.init(args);
//...
}
We'll come back to this bootstrapping part as it is not complete like this. For now, let's simply notice how
the plugin is registered.
Custom Request Context type
Creating a custom Request Context
type is optional but suggested. You can very well develop
a complete and production ready Spincast application without one... But it is a powerful feature as it allows
you to add functionalities to the Request Context
objects that are passed to your
Route Handlers
when request are received. You can learn more about this in the
Request Context section.
Note that if you use the Quick Start application as a template
for your application, a custom Request Context
type is already provided : you simply have to add
add-ons
to it, when required.
In this application, we will add a "httpClient()"
add-on
to our
custom Request Context
type, so we can easily make HTTP requests from
our Route Handlers
.
Here's the interface we are going to use for our custom Request Context
:
public interface AppRequestContext extends RequestContext<AppRequestContext> {
/**
* Add-on to access the HttpClient factory
*/
public HttpClient httpClient();
//... other add-ons
}
And an implementation for it :
public class AppRequestContextDefault extends RequestContextBase<AppRequestContext>
implements AppRequestContext {
private final HttpClient httpClient;
@AssistedInject
public AppRequestContextDefault(@Assisted Object exchange,
RequestContextBaseDeps<AppRequestContext> requestContextBaseDeps,
HttpClient httpClient) {
super(exchange, requestContextBaseDeps);
this.httpClient = httpClient;
}
@Override
public HttpClient httpClient() {
return this.httpClient;
}
}
Notice that we injected the HttpClient
component (9) which is in fact a factory to start new HTTP requests.
Our add-on
method, "httpClient()"
simply returns this factory (16).
You can learn more about the process of extending the Request Context
type in the dedicated section of the documentation.
Controller and Route Handlers
Let's now create a controller, some Route Handlers
, and use
the new add-on
we just added :
public class AppController {
/**
* Simple "Hello World" response on the "/" Route.
*/
public void indexPage(AppRequestContext context) {
context.response().sendPlainText("Hello World!");
}
/**
* Route Handler for the "/github-source/${username}" Route.
*
* We retrieve the HTML source of the GitHub page associated
* with the specified username, and return it in a Json object.
*/
public void githubSource(AppRequestContext context) {
String username = context.request().getPathParam("username");
String url = "https://github.com/" + username;
String src = context.httpClient().GET(url).send().getContentAsString();
JsonObject response = context.json().create();
response.set("username", username);
response.set("url", url);
response.set("source", src);
context.response().sendJson(response);
}
}
Explanation :
-
6 : A
Route Handler
for
the requests hitting the index page. Notice that we receive an
instance of our custom AppRequestContext
type as a parameter!
-
7 : Simple use of the
"response()" add-on
to send plain text.
-
16 : A second
Route Handler
for
a "/github-source/${username}"
Route, where a dynamic parameter
is used.
-
18 : We retrieve the value of the
"username"
dynamic
parameter from the request.
-
22 : We use the new
"httpClient()" add-on
we added to
our custom Request Context
type to retrieve the HTML source of the Github page associated
with that username.
-
24-27 : We create a JsonObject
as the response to send.
-
29 : We send the response as
"application/json"
.
Route definitions
In this tutorial, we won't define the Routes
in the App
class,
but directly in the controller! Let's do this :
public class AppController {
public void indexPage(AppRequestContext context) { ... }
public void githubSource(AppRequestContext context) { ... }
/**
* Init method : we inject the Router and then add some Routes to it.
*/
@Inject
protected void init(Router<AppRequestContext, DefaultWebsocketContext> router) {
router.GET("/").handle(this::indexPage);
router.GET("/github-source/${username}").handle(this::githubSource);
}
}
As you can see the Router is dynamic, you can inject
the Router in any component in order to add Routes
to it. Here, our controller simply uses
method reference to bind some of its own methods as Route Handlers
.
You may also notice that the type of the injected Router is Router<AppRequestContext, DefaultWebsocketContext>
,
which is kind of ugly.
It is so because we use a custom Request Context
type, and all components related to routing have to be aware of
it. We won't do it in this tutorial, but it's very easy to create a unparameterized version
of those routing components so they are prettier and easier to deal with!
Configurations
We are going to change the port the Server is started on by overriding the
default SpincastConfig binding
and use externalized configurations.
To do so, we create a custom class that extends the default
SpincastConfigDefault
implementation. Then, we use the special getters to find the values to use for our configurations :
public class AppConfig extends SpincastConfigDefault {
@Inject
protected AppConfig(SpincastConfigPluginConfig spincastConfigPluginConfig, @TestingMode boolean testingMode) {
super(spincastConfigPluginConfig, testingMode);
}
/**
* We change the port the Server will be started on.
*/
@Override
public int getHttpServerPort() {
Integer port = getInteger("server.port");
if (port == null) {
throw new RuntimeException("The 'port' configuration is required!");
}
return port;
}
/**
* It is recommended to *always* override the
* getPublicUrlBase()
configuration!
*/
@Override
public String getPublicUrlBase() {
return getString("api.baseUrl", super.getPublicUrlBase());
}
}
In this example, we do not provide a classpath configuration file to provide
default values for the externalized configurations, so it is required to provide
an explicit one! This is a YAML
file named by default "app-config.yaml"
, and has to be placed next
to the .jar
of the application before launching it.
Here's what it should look like :
server:
port: 12345
api:
baseUrl: http://localhost:12345
Application Guice module
Let's now create a Guice module for our application, and bind our custom components to it :
public class AppModule extends SpincastGuiceModuleBase {
@Override
protected void configure() {
bind(AppController.class).asEagerSingleton();
bind(SpincastConfig.class).to(AppConfig.class).in(Scopes.SINGLETON);
// ... other bindings
}
}
Explanation :
-
6 :
We bind our controller using asEagerSingleton() because it contains an
init method in which
Routes
are
defined and this must occur right when the application starts.
-
8 :
We change the default
SpincastConfig
binding so our custom
implementation class is used instead of the default one.
-
10 :
Notice that, in this application, we don't have many components! But, in a real life application,
this is where you are also going to bind your services, your repositories, your utilities.
The App class
The only missing piece is the App
class itself, where the main(...)
method and the Bootstrapper are defined :
public class App {
public static void main(String[] args) {
Spincast.configure()
.module(new AppModule())
.plugin(new SpincastHttpClientPlugin())
.requestContextImplementationClass(AppRequestContextDefault.class)
.init(args);
}
@Inject
protected void init(Server server) {
server.start();
}
}
Explanation :
-
3 : A standard
main(...)
method is the entry point of our application.
-
5 :
Spincast.configure()
starts the bootstrapper, so we can configure and initialize our application.
-
6 : We register our application module.
-
7 : We register the
Spincast HTTP Client
plugin.
-
8 : Since we use a custom
Request Context
type,
we need to let Spincast know about it.
-
9 : The
init()
method will create the Guice
context, will bind the current App
class in the context and will load it.
We are also using it to bind the arguments passed to the main(...)
method... Doing so, we can then inject them using the @MainArgs String[]
key.
-
12-13 : This init method
is going to be called automatically when the Guice context is ready. We inject in it the
Server. We don't need to inject
the
Router
in this example since the Routes
are defined in the
controller.
-
14 : We start the server.
Try it!
Download this application, and run it by yourself :
-
Download spincast-demos-supercalifragilisticexpialidocious.zip.
-
Unzip the file.
-
Enter the root directory using a terminal.
-
Compile the application using Maven :
mvn clean package
-
Make sure you copy the configuration file "
app-config.yaml
", provided at the
root of the sources, next to the generated .jar
file, inside the "target" folder...
Otherwise you are going to have an exception when starting the application!
-
Launch the application :
java -jar target/spincast-demos-supercalifragilisticexpialidocious-{{spincast.spincastCurrrentVersion}}.jar
The application is then accessible at http://localhost:4242
Don't forget to try the "github-source" Route (you can change the "username" dynamic parameter) :
http://localhost:4242/github-source/spincast.
Too complex?
Try the Quick "Hello World!" demo, for an easier version!
{% endblock %}