pippo-java / pippo Goto Github PK
View Code? Open in Web Editor NEWMicro Java Web Framework
Home Page: http://www.pippo.ro
License: Apache License 2.0
Micro Java Web Framework
Home Page: http://www.pippo.ro
License: Apache License 2.0
Maybe we should have a changelog? @gitblit
Very good idea. I think that a changelog is a must have.
In the past I tried to use github Releases but without much success. Do you prefer the automatic approach via github Releases or the manual approach? If you prefer the manual approach who do you see the implementation (a changelog file, ...).
As I saw on their site the Rhythm template engine seems to have good performance:
"Being different from dynamic engines like Velocity and FreeMarker, Rythm is a static typed engine and compile your template source into java byte code, and thus very fast. Based on the result of this benchmark test, Rythm is one of the fastest template engines in Java world."
I want to know if it will worth the effort of integrating it.
Consider this scenario where we are getting a Db instance to be shared throughout a request-response cycle:
// Preparation route for
ALL("/.*", (request, response, chain) -> {
response.bind("db", getDb());
});
POST("/{id}", (request, response, chain) -> {
Db db = (Db) response.getLocals().get("db");
// this throws an runtime exception somewhere deep inside
db.insert(something);
});
// Cleanup route
ALL("/.*", (request, response, chain) -> {
Db db = (Db) response.getLocals().remove("db");
db.release();
});
If an exception is thrown in POST
then the cleanup route is never executed and we are now leaking Db connections. Instead of trapping Db exceptions in every handler, it would be nice to be able to specify cleanup routes/routines that are guaranteed to execute at the end of the Request-Response cycle despite exceptions within the chain. These mechanisms should be fail-safe themselves and should not have a chain
parameter.
void cleanup(Request request, Response response);
The RouteHandlerChain
or PippoFilter
should execute these cleanup routines within independent try {} catch {}
blocks.
for (CleanupHandler handler : cleanupHandlers) {
try {
handler.cleanup(request, response);
} catch (Exception e) {
log.error("Whoops", e);
}
}
Perhaps these could be registered as:
// Cleanup route
CLEANUP("/.*", (request, response) -> {
Db db = (Db) response.getLocals().remove("db");
db.release();
});
A nice idea from wicket-bootstrap implementation of webjars was to use the current
to get the latest version of the webjar, so if you modify the pom.xml to get other dependecy you shouldn't modify all the html or templates. Of course this should be optional and if the user wants to use the fixed version string the scripts should be loaded.
I've tested a JSON sample like this with the 0.4.2 release:
public class PippoController extends Application{
@Override
protected void onInit() {
GET("/shop", new RouteHandler() {
public void handle(RouteContext routeContext) {
Shop shop = createShop();
routeContext.json().send(shop);
}
});
}
private Shop createShop(){
Shop shop = new Shop();
shop.setName("KFC");
shop.setStaff(new String[]{"messi","ronaldo"});
return shop;
}
}
The output was:
curl localhost:8338/shop
<html><body><div>Cannot find a route for 'GET /shop'</div><div>Available routes:</div><ul style=" list-style-type: none; margin: 0; "><li>GET /shop</li></ul></b
And the error was:
ro.pippo.core.PippoRuntimeException: You must set a content type engine for 'application/json; charset=UTF-8'
at ro.pippo.core.Response.send(Response.java:818)
at ro.pippo.core.Response.send(Response.java:809)
at ro.pippo.core.route.DefaultRouteContext.send(DefaultRouteContext.java:223)
at ro.test.pippo.PippoController$1.handle(PippoController.java:17)
at ro.pippo.core.route.DefaultRouteContext.handleRoute(DefaultRouteContext.java:339)
at ro.pippo.core.route.DefaultRouteContext.next(DefaultRouteContext.java:249)
In templating we often need to do -
<#if (session.username)??>
<li class="active"><a href="${contextPath}/home">Home</a></li>
<li><a href="#">${session.username}</a></li>
<li><a href="/logout">${i18n("header.logout")}</a></li>
<#else>
<li><a href="/login" id="login">${i18n("header.login")}</a></li>
</#if>
I tried dirty hacking to implement it --
public class ro.pippo.core.route.RouteDispatcher {
protected void onRouteDispatch(Request request, Response response) {
//....
if (routeMatches.isEmpty()) {
errorHandler.handle(HttpConstants.StatusCode.NOT_FOUND, routeContext);
} else {
processFlash(routeContext);
processSessionAttributes(routeContext); //my line
}
}
/** My addition --
* Makes attributes in session available through a map
* @param routeContext
*/
private void processSessionAttributes(RouteContext routeContext) {
Map<String, Object> sessionAttributes = new HashMap<>();
if (routeContext.hasSession()) {
Session session = routeContext.getSession() ;
Enumeration<String> enumeration = routeContext.getSession().getNames();
while(enumeration.hasMoreElements() ) {
String key = enumeration.nextElement();
if("flash".equalsIgnoreCase(key)){
continue;
}
sessionAttributes.put(key, session.get(key));
}
// make current session objects available to templates
routeContext.setLocal("session", sessionAttributes);
}
}
}
And
public final class ro.pippo.core.route.DefaultRouteContext {
//....
@SuppressWarnings("unchecked")
@Override
public <T> T setSession(String name, T t) {
getSession().put(name, t);
Map<String,Object> values =(Map<String,Object>) getLocal("session");
if(values==null){
values=new HashMap<>();
setLocal("session", values);
}
values.put(name, t);
return t;
}
}
Note sure the best way to do this. But it kinda works.
I created a new branch cookie_session
where I try to create a proof of concept for a session implementation based on cookie.
I will try to put in this issue all problems/inconvenient found by me.
For now I found this problem:
CookieSession
I need to have access to response (to save the session cookie in response)I see these possible solutions:
RouteContext
(#91) but in the form:public interface RouteContext {
getRequest(); // or request()
getResponse(); // or response()
next()
}
The idea is to keep Request
, Response
concepts and to add more helper merthods (for example getApplication() or getPippoSettings()). Also I think that it's good to move getSession
method from request and to put this method in RouteContext
for example. In this mode I can change the signature of SessionFactory.getSession(Request, boolean)
to getSession(RouteContext)
.
I wrote in a previous comment:
This RouteContext object maybe is valuable in the future if we want to put some business (methods) - of course is not ok to add more parameters in handleRoute and in this scenario we must create this context object (bucket).
Maybe it's that time ๐
Application.get()
) other information like request (Request.get()) , response, sessionFirst solution is the Ninja way, it's simple to implement but RouteContext has to walk through the entire application as parameter. The second solution is the Wicket way, it's simple to implement, you can access these objects from anywhere in application but probably will be sensible in async mode (if you plan to do something).
What is your opinion? Do you see other options?
I think the maven profile "main" should be renamed with "standalone" for clearer usage.
I found that the current way of defining the application seems somehow monolithic:
public class AjaxApplication extends Application {
@Override
protected void onInit() {
...
}
}
...
public static void main(String[] args) {
Pippo pippo = new Pippo(new AjaxApplication());
pippo.start();
}
instead of
@PippoExtension
public class AjaxApplication extends Application {
@Override
protected void onInit() {
...
}
}
...
public static void main(String[] args) {
Pippo pippo = new Pippo();
pippo.start();
}
Wouldn't be better if we could have multiple classes that extends Application class and have them
annotated with a marker so Pippo will read them on load? This way you gain more in modularity and let the developer needs split the code.
You can even implement it with the pf4j framework, but this I think will increase the memory footprint of the core framework.
It will be like butter on toast for microservices applications! ๐ฅ
We need to be able to generate context-aware urls from within a template. I'm not a big fan of the urlFor(Class, "method")
or urlFor(String, Map)
methods because they encourage repeated fragile coding. The are necessary methods, however, to build upon for what I am proposing.
I think the best approach is to implement a per-engine mechanism to register custom helper functions. This would still require the fragile urlFor(Class, "method")
or urlFor(String, Map)
methods but you'd write those calls once per route/url.
In the template engine you would write something like:
Freemarker
<a href="${myUrl(arg1, arg2)}">${arg2}</a>
Jade
a(href=app.myUrl(arg1, arg2))
=arg2
Trimou
<a href="{{ myUrl arg1 arg2 }}">{{arg2}}</a>
Pebble
<a href="{{ myUrl (arg1, arg2) }}">{{arg2}}</a>
Groovy
a(href: myUrl (arg1, arg2)) { arg2 }
In RouterDispatcher.onRouteDispatch()
the value for requestPath
is not correct calculated. The correct calculation is in PippoFilter.getRelativePath()
that by the way is not used anymore.
Hi,
I have been watching this repo for some time now. Good development so far!
I m planning to build a project using this framework. However, I have certain use cases for interceptor.
Any plan to support it?
Load translations dynamically (from outside the classpath) without adding too much complexity. The data source for translations could be a service (like Redis) or simple files for each language.
The new approach could have a method for reload translations in memory (programmers can make a /admin/i18n/reload URL to call that method), and a method to log terms/translations not found (to help translators to add new ones).
@Autowired fields do not get injected, only @Inject is considered.
Can you add a template module for Apache Velocity ?
Thanks so much !
A new package core.handler
and move all handlers (*Handler.java
, route.*Handler.java
) in this package.
The project needs a formatter definition shared in the repo root for line endings, imports ordering, trailing whitespace, etc. I'm running Eclipse, not sure what you are using these days. Once you've established a style you are happy with we should re-format all files and add a .gitattributes file to help ensure the files are checked-out/in as intended.
BTW, your source headers are formatted oddly.
Http is flexible and allows you to define your own verbs. Having said that, PATCH is a somewhat commonly used method for updating selected fields within an entity rather than inserting or replacing an entity.
I've been running Pippo on a couple containers and I have identified some under-the-hood issues.
PippoFilter
is not necessarily loaded on startup of the container I think because it is a Filter instead of a Servlet. This is an issue because I'd rather see Pippo complete it's initialization on startup rather than on the first request. We might consider switching PippoFilter
to be PippoServlet
. We don't lose anything AFAICS.PippoFilter
is called by the container for matching path requests (e.g. /*
). That means the response supplied to PippoFilter
starts off at 200 OK
. We then set status(Integer.MAX_VALUE)
to indicate Pippo has not properly processed the response and we require that some handler in the chain to set the status to a valid value.Unfortunately, some containers dislike status(Integer.MAX_VALUE)
and other containers dislike status(0)
.
So one of the following needs to happen...
Response
to indicate we have a set status (boolean statusSet = false;
)Response
that is used to set the status int in HttpServletResponse
at the end of the request.I have two more unreleased modules that I wrote for Ninja. One of them is full Thymeleaf integration.
Thymeleaf, favored by Spring, has some compelling features. Generally I have found it 2x slower than most other template engines, but it's Wicket-ish, being an XHTML-based engine, and validates the document structure. Plus, the Thymeleaf devs have been working on version 3 which apparently boosts performance and might make it more competitive with the likes of Pebble, Freemarker, & Trimou.
In order to have full integration you need to provide HttpServletRequest
, HttpServletResponse
, & ServletContext
to the engine at rendering time. Without full integration, Thymeleaf is not as appealing.
If you are open to a little refactoring/redesigning of the internal interfaces, then I can bring this integration to Pippo.
I'm testing the waters before devoting any energy to this topic.
Wanting to change the default port for the sample application with the code
public static void main(String[] args) {
Pippo pippo = new Pippo();
WebServerSettings settings = new WebServerSettings().port(8080);
JettyServer server = new JettyServer();
server.setSettings(settings);
pippo.setServer(server);
pippo.getApplication().GET("/", new RouteHandler() {
@Override
public void handle(RouteContext routeContext) {
routeContext.send("Hello World!");
}
});
pippo.start();
}
led me to the error:
Exception in thread "main" java.lang.NullPointerException
at ro.pippo.jetty.JettyServer.createPippoHandler(JettyServer.java:108)
at ro.pippo.jetty.JettyServer.start(JettyServer.java:63)
at ro.pippo.core.Pippo.start(Pippo.java:81)
at ro.test.pippo.App.main(App.java:27)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)
Hello,
I am trying to setup a test project using the maven quickstart archtype (0.5.0). It works perfect with the default settings. When I try to access a URL which is not defined (/login) I get a nicely rendered 404.
However, when I switch the Template Engine from Freemarker to Pebble, I get the following exception:
00:32:31 [qtp1486371051-18] DEBUG ro.pippo.core.route.RouteDispatcher - Returned status code 404 for GET '/login'
00:32:31 [qtp1486371051-18] WARN org.eclipse.jetty.servlet.ServletHandler - /login
ro.pippo.core.PippoRuntimeException: Recursion in error handler
at ro.pippo.core.DefaultErrorHandler.checkForRecursion(DefaultErrorHandler.java:311)
at ro.pippo.core.DefaultErrorHandler.handle(DefaultErrorHandler.java:171)
at ro.pippo.core.route.RouteDispatcher.onRouteDispatch(RouteDispatcher.java:164)
at ro.pippo.core.route.RouteDispatcher.dispatch(RouteDispatcher.java:99)
at ro.pippo.core.PippoFilter.doFilter(PippoFilter.java:139)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:585)
at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:221)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1127)
at ro.pippo.jetty.JettyServer$PippoHandler.doHandle(JettyServer.java:148)
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:515)
at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97)
at org.eclipse.jetty.server.Server.handle(Server.java:499)
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:310)
at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:257)
at org.eclipse.jetty.io.AbstractConnection$2.run(AbstractConnection.java:540)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:635)
at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:555)
at java.lang.Thread.run(Thread.java:745)
I created a new branch controller_module when I move the Controller
concept in a new module pippo-controller
.
I see now that I made a mistake - I created this branch not from master
(from cookie_session
branch) - but I can fix this very easy.
I created some extensions like: ControllerApplication extends Application
, ControllerRouter extends Router
, ... Fill free to propose other names or other approaches.
We have a little problem with MetricsRouteContext
from pippo-metrics
but I think that we can fix it. The problem is that in this class we have:
if (handler instanceof ControllerHandler) {
ControllerHandler controllerHandler = (ControllerHandler) handler;
method = controllerHandler.getMethod();
}
and I don't want to add a dependency to pippo-controller
module for this reason. I think that we can resolve this problem with reflection.
I'm on the fence on this issue. Supporting inconsistencies with other containers is always a nuisance. Having a single deployable product that behaves in production exactly as in development is pretty handy.
The biggest thing we lose is container authentication - but I have always implemented my own anyway.
Either way we need to decide if Pippo apps require their own dedicated container or if they can be distributed as WARs.
FileResourceHandler constructor is taking two parameters "url" and "directory"
https://github.com/decebals/pippo/blob/master/pippo-core/src/main/java/ro/pippo/core/route/FileResourceHandler.java
making it confusing to use it like:
GET("/url", new FileResourceHandler("url", "directory"));
it's intended to be used with:
GET(new FileResourceHandler("url", "directory"));
I wish to review the Pippo switch to J8.
The idea is not new, we have some discussions on this theme in #41 and #43. We have some benefits : Lambda (short route handler registration), Optional and maybe more. The main problem is if we want to move pippo to J8 or to create a new branch (or maybe pippo2) and when we start work on this feature,
I am waiting opinions.
I see that ThreadContext is no longer used so I prefer to remove it.
I see that many java web frameworks use ThreadLocal (Spring MVC, Wicket, Vaadin, Play Framework - see thread local context propagation). It's not safe but help a lot. I dislike to pass as parameter a context object in 80% of framework methods.
I'm trying to reconcile the design goal of minimal dependencies with not having to re-invent utility functions, etc. Having been far down the road of writing and unit testing my own utility functions I no longer think that is a good approach just to keep a core small. It ends up costing you more than you initially save.
I think that Guava brings some good stuff to the table, but it also adds 2 MB to your footprint. Commons-lang3 is also handy and that brings a 600 KB footprint. How do you feel about one or both of those? Guava I can make immediate use of with the Optional
type. Commons-lang3 has some really great string handling functions that I don't need at the moment but might be useful when auditing the base and/or adding new functionality.
The browser often reports a locale. Template engines need this for localized page generation.
Managing browser resources is a pain. Webjars makes this much less painful. Pippo would benefit from a webjars routehandler.
Pippo should automatically handle stamping served resources with etags in PROD mode. Take a peek at how Ninja does this.
Make options (threads, buffer size, etc.) for each webserver accesible from settings files. This is specially usefull for Undertow, where speed varies with response size and buffer size needs to be configured.
I don't know if it's intentional or not, it seems in the sources there is a comment.
https://github.com/decebals/pippo/blob/cbb8863bb0dbee1f4154a90774144a3e2dbdeeb6/pippo-core/src/main/java/ro/pippo/core/route/DefaultRouter.java#L407
Maybe I'm used to wicket but I was expecting the value to be automatically encoded.
Markdown? Asciidoc? Framework for building a static site?
How do you feel about adopting one of the following signatures:
void handleRoute(Context context, RouteHandlerChain chain);
or my preference
void handleRoute(Context context);
where Context
is an interface like:
interface Context {
Request request();
Response response();
}
If we put the chain inside the Context
then Context
would need a next()
method.
void handleRoute(Context context) {
if (someCondition) {
context.next();
}
}
I would drop the Request
and Response
factories and implement a ContextFactory
.
I would also move most/all functionality out of Request
and Response
into Context
and not wrap HttpServletRequest/Response at all.
interface Context {
HttpServletRequest request();
HttpServletResponse response();
void next();
// refactored methods from Request
// refactored methods from Response
}
We have alot of boilerplate with passing around three args all the time. Customization often requires having both Request
and Response
so I would prefer to merge all these concepts into one type/argument. From what I see most other frameworks have also come to this conclusion: Wicket, Ninja, Ratpack, etc.
I can live with two args if you are opposed to putting the chain inside the Context
.
I haven't used it much but fastjson claims to be, well, fast. It is also smaller than gson. I'm still a gson fan, but it's good to be open-minded.
Using pippo-controller 0.5.0-SNAPSHOT version and hitting
Caused by: java.lang.ClassCastException: ro.pippo.core.route.DefaultRouter cannot be cast to ro.pippo.controller.ControllerRouter
at demo.controller.ContactsController.uriFor(ContactsController.java:44)
My investigation points the problem to ro.pippo.controller.ControllerApplication.
The problem is resolved if I override the following methods,
public class ControllerApplication extends Application {
private DefaultControllerRouter router;
//other methods as they are
@Override
public Router getRouter() {
if (router == null) {
router = new DefaultControllerRouter();
}
return router;
}
@Override
public void setRouter(Router router) {
super.setRouter(router);
}
}
Parameters as Arrays are written in the url by
private String uriFor(PatternBinding binding, Map<String, Object> parameters) {
...
query.append(parameterName).append("=").append(parameterValue.toString());
...
This generates some parameters like &tag=[phone,tablet]
which in turn gets deserialized by ParameterValue class:
public List<String> toList(List<String> defaultValue) {
if (isNull()) {
return defaultValue;
}
if (values.length == 1) {
return Arrays.asList(values[0].split(","));
}
return Arrays.asList(values);
}
which results in a list of:
"[phone", "tablet]"
With the inclusion of the classpath resource handlers I don't think there is much value in this anymore.
Those who wish to serve external files are likely to want some type of security controls which this mechanism does not support. I think it makes sense to remove it.
Feedback/Flash messages are only available to the next request.
The problem is that if I want to create an WAR for CrudNgApplication (an example), this WAR file has around of 20MB because I put all demo jars (jetty, spring, trimou, ...). Probably the real size is around of 200KB. I think that it's very important to show that a pippo application has a small footprint.
My intention is to split pippo-demo in multiple maven module (a folder, an application and a pom.xml for each module).
The new structure could be:
pippo-demo
pippo-demo-simple
pippo-demo-crud
pippo-demo-crudng
pippo-demo-controller
pippo-demo-upload
pippo-demo-spring
pippo-demo-guice
pippo-demo-validation
It's a problem with pippo-template that already contains multiple application (one for each template engine).
Do you see other options?
Localized template engines should have a reference to a messages class for loading language resources. See Ninja.
First, it converts the input stream using the default charset. So if I post a data using application/octet-stream with a charset different from the default charset, some characters will be impossible for reverse conversion.
Another thing is that there is some time when I really want to process stream-based binary data, but Request don't have the option.
Please consider exposing the stream to developers.
Add support for Ajax requests
Maven archetype to build a small Pippo web application.
In Response.commit()
we are flushing the buffer which forces chunked transfer encoding and finalizes the servlet response.
It feels wrong that we are doing this. I can't point to documentation that says this is a problem, but I can look at other frameworks and see they do not flush anything and thus do not force chunked encoding.
I think we need to consider some alternative (internal) mechanism for finalizing a response.
I try to run CrudNgApplication from pippo-demo as WAR on a tomcat 7. The problem is that I encountered a strange issue:
Resource interpreted as Stylesheet but transferred with MIME type application/octet-stream: "http://localhost:8080/webjars/bootstrap/3.3.1/css/bootstrap.min.css".
login:41 Resource interpreted as Script but transferred with MIME type application/octet-stream: "http://localhost:8080/webjars/jquery/1.11.1/jquery.min.js".
login:42 Resource interpreted as Script but transferred with MIME type application/octet-stream: "http://localhost:8080/webjars/bootstrap/3.3.1/js/bootstrap.min.js".
login:43 Resource interpreted as Script but transferred with MIME type application/octet-stream: "http://localhost:8080/webjars/angularjs/1.3.6/angular.min.js".
and of course that I cannot use the webjars defined in head
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.