A micro HTTP framework based on Netty for Java.
- JDK8+
- Eclipse
<dependency>
<groupId>com.pr-ing</groupId>
<artifactId>lucy</artifactId>
<version>$VERSION</version>
</dependency>
- Define an entry point:
package entry;
import org.pring.lucy.server.Server;
public class Main extends Server {
public static void main(String[] args) throws Exception {
new Main()
// .withoutCookies()
// .sessionAge(60)
// .gzip()
// .database("jdbc:mysql://localhost:3306/information_schema", "root", "root")
// .production()
.staticLocation("/var/www")
.port(8080)
.serve();
}
}
- Define a handler class:
package root.controller;
import org.pring.lucy.server.HttpController;
public class Index extends HttpController {
@Override
public void index() throws Exception {
echo("Hello World");
}
}
- Test it:
http://localhost:8080/
- or -
http://localhost:8080/index
- or -
http://localhost:8080/:index/index
- or -
http://localhost:8080/root:index/index
^ ^ ^
| | method name
| class name
package name
Create a root.view
package
and a matching html
template in it if you want templating. So if you have Index.java
you must have index.html
(lowercase) in root.view
.
/ -> binds to root.*
...
new Main()
// 1. withoutCookies()
// 2. sessionAge(60)
// 3. gzip()
// 4. database("jdbc:mysql://localhost:3306/information_schema", "root", "root")
// 5. production()
// 6. staticLocation("/var/www")
// 7. port(8080)
// 8. epoll()
// 9. sync()
// 10. mqtt(broker, listener, topic)
// 11. serve();
...
withoutCookies()
Will not set any cookies at all. In case you have a static website.sessionAge(60)
Defines session age in seconds.gzip()
Will gzip the response sent to client.database("jdbc:...", "user", "pass")
Will initiate a database pool.production()
Will load the clases only once. No hot reloading. It must be uncommented when exporting to final JAR.staticLocation("absolute path")
Sets the location of static files.port(8080)
Sets the port where to listen.epoll()
Enables native Linux epoll.sync()
Joins the main thread and blocks. Omit this if you have anything else after (which of course blocks main thread).mqtt(broker, listener, topic)
Connects to a specifiedbroker
and handles incoming messages through aMqttCallbackListener
coming to subscribedtopic
. Use#
as a wild card.publish(topic, message)
will publish your messages from any controller.serve()
Kicks off everything.
- Hot reloading
- Templating
- Sessions
- Database
- Very simple interface
- Lightweight and somewhat fast
Method | Function |
---|---|
redirect(to) |
Redirects to specified URL |
redirectWithTemplate(to, templateName) |
Redirects to specified URL and sets a desired template |
status(code) |
Sets HTTP status code, overrides @Status annotation |
method() |
Returns request HTTP method |
halt() |
Halts execution |
halt(status) |
Halts execution with status code |
halt(status, message) |
Halts execution with status code and a message |
header(name) |
Gets header name from request |
header(name, value) |
Sets a header on response |
headers() |
List of request headers |
cookie(name) |
Gets cookie name |
cookie(name, value) |
Sets cookie value to name on response |
cookie(name, value, ttl) |
Sets cookie value and age to name on response |
cookie(name, value, ttl, isSecure) |
Sets cookie value and age and security to name on response |
cookie(name, null) |
Removes cookie |
cookies() |
Gets cookie list |
queryParams(key) |
Gets a query param List for key |
GET(key) |
Gets a String value for key |
GETint(key) |
Gets an int value for key |
GETdouble(key) |
Gets a double value for key |
POST(key) |
Gets a String value for key |
POSTint(key) |
Gets an int value for key |
POSTdouble(key) |
Gets a double value for key |
session(key) |
Gets session value for key |
session(key, value) |
Sets session value for key |
session(key, null) |
Removes key from session |
session() |
Gets a key, value Map |
sessionId() |
Gets session id |
sessionDestroy() |
Destroys session |
echo(msg) |
Echoes stuff |
debug(msg) |
Prints stuff on running terminal |
view(key, value) |
Passes key and value to template |
template(name) |
Easy template selection (overrides @View(name) ) |
file(name) |
Gets File from uploaded file |
files() |
Gets a List with uploaded File s |
sendFile(path) |
Forces browser to download given file from path |
sendFile(data[], name) |
Creates a temporary file from data and sends to client |
clientIp() |
Gets client IP |
port() |
Gets client port |
requestMethod() |
Gets HTTP request method |
url() |
Gets request URL |
userAgent() |
Does not work |
isKeepAlive() |
Gets connection type |
SqlDB.query("QRY") |
Returns a one row CachedRowSet . Use this if you're not going to loop through results |
SqlDB.query("QRY", args) |
Returns a one row CachedRowSet . Use this if you're not going to loop through results |
SqlDB.selectCell("QRY") |
Returns one row with one cell |
SqlDB.selectCell("QRY", args[]) |
Returns one row with one cell |
SqlDB.select("QRY") |
Returns an Iterable so you can loop through it |
SqlDB.select("QRY", args) |
Returns an Iterable so you can loop through it |
SqlDB.insert("QRY", args) |
Returns mysql-last-id |
SqlDB.update("QRY", args) |
Returns manipulated row nums |
SqlDB.delete("QRY", args) |
Returns manipulated row nums (Alias to .update ) |
publish(topic, message) |
If mqtt is called on Main then this will publish a message to a desired topic. |
broadcast(message) |
If you have a @WebSocket method then this will broadcast a message to all active WebSocket channels. |
Annotation | Function |
---|---|
@API |
Marks method as an API method and ignores templating |
@API("text/plain") |
Marks method as API and sets Content-Type and ignores templating |
@NoSession |
Does not bother with session stuff |
@Status(200) |
Sets HTTP response code |
@View("index1") |
Sets a custom view |
@View("") |
Removes associated template |
@NoAccessCheck |
Does not check hasAccess() which returns boolean, for e.x: isLoggedIn |
@WebSocket |
Marks method as WebSocket handler |
Say you have an API with buyer and seller. You can have the code separately like this:
buyer.controller.*
buyer.view.*
- and -
seller.controller.*
seller.view.*
buyer.controller.Index
:
package buyer.controller;
import org.pring.lucy.server.HttpController;
public class Index extends HttpController {
@Override
public void index() throws Exception {
view("myVar", aVeryComplexCalculation());
}
private static int aVeryComplexCalculation() {
return 42;
}
}
buyer.view.index
:
<B> {{ $myVar; }} </B>
Then access it like this:
http://localhost:8080/buyer:/
seller.controller.Index
:
package seller.controller;
import org.pring.lucy.server.HttpController;
public class Index extends HttpController {
@Override
@API
public void index() throws Exception {
for (ResultSet r : SqlDB.select("select * from `sellers`;")) {
echo(r.getString("id") + " => " + r.getString("name") + '\n');
}
}
@API
public void update(int id, String name) {
SqlDB.update("UPDATE `sellers` SET `name` = ? WHERE `id` = ?", new Object[] { name, id });
redirect("/");
}
}
Then access it like this:
http://localhost:8080/seller:/
Or update like this:
http://localhost:8080/seller:index/update/1/Awesome
^ ^ ^ ^ ^
| | | type safe args: id/name
| | method name
| class name
package name
Arguments are typesafe, so if you try to pass a String
where int
is expected, you'll get an exception if you're under development mode, otherwise it will politely redirect you to /
.
Template engine is pure Java code plus for a short hand when writing for
loops:
<ul>
{{
for (String s : String[] { "first" }) {
<li>$s;</li>
}
}}
</ul>
... and include
:
{{ include $myTemp; }}
- or -
{{ include "some.other.package.file" }}
- or -
{{ include "someOtherFile" }}
- or -
{{ include "/var/www/file/on/disk.html" }}
Database access obviously:
<ul>
{{
for (ResultSet r : SqlDB.select("select * from `sellers`;")) {
<li>$r.getString(1);</li>
}
}}
</ul>
Also Session through a session
variable:
I'm {{
if (session.getBoolean("isLoggedIn"))
echo("<b>logged in. WOHOOOO</b>");
else
echo("<i>OUT :(</i>");
}}
- Maven
- WebSockets
-
Spdy - MQTT
- Some Optimizations
- Writing code with Xtend
https://github.com/betim/Lucy/releases/tag/1.1
- Netty 4.0.37.Final
- commons-lang3 3.4
- HikariCP 2.4.6
- slf4j-api 1.7.21
- mysql-connector-java 5.1.36
- org.eclipse.paho 1.0.2
- Currently works only on Unix/Linux.
Copyright 2015 Lucy
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.