The BeerConfig class represents the configuration for the
Beer server,
including IP, port, Jetty thread pool, SSL, and logger settings. All setters return
BeerConfig for method chaining.
Full beer config example
new BeerConfig( )
.setIp( "0.0.0.0" )
.setPort( 8080 )
.setLoggerName( "beer" )
.setJettyMinThreads( 4 )
.setJettyMaxThreads( 32 )
.setJettyIdleTimeout( 60000 );
.setKeystorePath( "path/to/keystore" ); // For HTTPS
.setKeystorePass( "yourPassword" );
Initializes the Beer server with the given configuration.
Beer beer = new Beer( );
BeerConfig config = new BeerConfig( );
beer.init( config );
Starts the server and binds all configured routes and filters.
beer.start( );
Returns the underlying Jetty Server instance for advanced
manipulation.
Server server = beer.getServerInstance( );
Define HTTP routes for GET, POST, PUT, DELETE requests.
beer.get( "/api/users", ( req, res ) -> { ... } );
beer.post( "/api/users", ( req, res ) -> { ... } );
Define HTTP routes for preprocess requests.
Registers a filter ( middleware ) for a specific path.
beer.filter( "/api/*", ( req, res ) -> {
System.out.println( req.getRequestURI( ) );
} );
Adds global exception handling, returning proper HTTP error codes and messages. Beer auto generates an error tag and a basic error message for corresponding error code.
In case of custom error responses do not use this filter and implement your own exception filter at "/*" path using filter( ) method.
Enables CORS for all routes, allowing cross-origin requests.
Logs all HTTP requests to the configured logger.
Define WebSockets with ease. Beer has an internal map that stores connected clients for you.
Registers a WebSocket endpoint with a callback for incoming messages.
beer.socket( "/ws/chat", msg -> System.out.println( msg ) );
Sends a message to all connected WebSocket sessions on a specific path.
beer.broadcast( "/ws/chat", Map.of( "message", "Hello everyone!" ) );
Define static file path. Must be a folder inside standalone fat jar.
Serves static files from the classpath at the specified path.
beer.staticFiles( "/docs", "/static" );
Reads and returns the raw body of an HTTP request as a String.
String body = BeerUtils.parseReqBody( req );
Parses the request body JSON into an object of the specified class.
MyObject obj = BeerUtils.parseBody( req, MyObject.class );
Parses query parameters into an object of the specified class.
MyParams params = BeerUtils.parseQueryParams( req, MyParams.class );
Retrieves a path parameter value from the request.
String userId = BeerUtils.getPathParam( req, ":userid" );
Parses Authorization header and returns username/password
credentials.
Credentials creds = BeerUtils.parseBasicAuthCredentials( req );
Stores a user object in the request attributes for later retrieval.
BeerUtils.setReqUser( req, user );
Retrieves a user object previously stored in the request.
User user = BeerUtils.getReqUser( req );
Returns a specific segment of the request URI.
String segment = BeerUtils.getPathSegment( req, 2 );
Sends an HTTP redirect to the client.
BeerUtils.redirect( res, "/home" );
Converts an object, collection, map, or string into a JSON-formatted string.
String jsonString = BeerUtils.json( myObject );
Uploads a file from a multipart request to the specified location using default configuration.
SimpleMessage msg = BeerFileUtils.uploadFile( req, res, "file", "/uploads" );
Uploads a file with a custom configuration for maximum file size, request size, and file threshold.
BeerFileConfig config = new BeerFileConfig( ).setMaxFileSize( 5000000 );
SimpleMessage msg = BeerFileUtils.uploadFile( req, res, "file", "/uploads", config );
Downloads a file from the server, either inline or as an attachment.
true to display in browser,
false to force
download.
BeerFileUtils.downloadFile( req, res, "/uploads", "file.png", true );
Deletes a file from the server.
SimpleMessage msg = BeerFileUtils.deleteFile( req, res, "/uploads", "file.png" );
Lists all files in a directory, sorted by last modification date.
List<String> files = BeerFileUtils.listFiles( "/uploads" );
Generates a random secret key using SHA-256 hash of a UUID, suitable for JWT signing.
String secretKey = BeerAuthUtils.generateRandomSecretKey( );
Generates a signed JWT token with a subject and expiration time.
String jwtToken = BeerAuthUtils.generateJwt( secretKey, 60, "user123" );
Verifies a JWT token and returns its claims. Throws UnauthorizedException if invalid or
expired.
JWTClaimsSet claims = BeerAuthUtils.verifyJwt( secretKey, jwtToken );
A fully functional production ready real world application made with beer in just one file and a few lines of code. For database operations library sqlemur has been used.
package gr.kgdev.emsrally;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.codec.digest.DigestUtils;
import gr.kgdev.beer.core.Beer;
import gr.kgdev.beer.core.BeerConfig;
import gr.kgdev.beer.model.SimpleMessage;
import gr.kgdev.beer.model.exceptions.BadRequestException;
import gr.kgdev.beer.model.exceptions.ForbiddenException;
import gr.kgdev.beer.model.exceptions.UnauthorizedException;
import gr.kgdev.beer.utils.BeerAuthUtils;
import gr.kgdev.beer.utils.BeerUtils;
import gr.kgdev.beerutils.PropertiesLoader;
import gr.kgdev.emsrally.model.EmsRallyUser;
import gr.kgdev.emsrally.model.EmsRallyUserRole;
import gr.kgdev.emsrally.model.Management;
import gr.kgdev.emsrally.model.SaveScoresBody;
import gr.kgdev.emsrally.model.Scenario;
import gr.kgdev.emsrally.model.SaveScoresBody.Score;
import gr.kgdev.sqlemur.core.SqlConnector;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
public class EmsRallyBeerApp {
private static final String ROLES_QUERY =
"""
select r.name
from user_roles r, user_has_role uhr, users u
where 1=1
and uhr.user_id = u.id
and r.id = uhr.role_id
and u.id = ?
""";
private Beer beer = new Beer( );
private SqlConnector sqlConnector;
private String secretKey;
public static void main( String[] args ) throws Exception {
new EmsRallyBeerApp( ).start( );
}
private void start( ) throws Exception {
String ip = PropertiesLoader.getProperty( "server.ip", String.class );
Integer port = PropertiesLoader.getProperty( "server.port", Integer.class );
String dbUrl = PropertiesLoader.getProperty( "db.url", String.class );
String dbUsername = PropertiesLoader.getProperty( "db.username", String.class );
String dbPassword = PropertiesLoader.getProperty( "db.password", String.class );
sqlConnector = new SqlConnector( "com.mysql.cj.jdbc.Driver", dbUrl, dbUsername, dbPassword );
sqlConnector.enableCcMode( true );
secretKey = BeerAuthUtils.generateRandomSecretKey( );
var config =
new BeerConfig( ).setIp( ip )
.setPort( port )
.setLoggerName( "beer" )
.setJettyMinThreads( 4 )
.setJettyMaxThreads( 32 )
.setJettyIdleTimeout( 60000 );
beer.init( config );
// FILTERS
beer.corsAllFilter( );
beer.exceptionFilter( );
// authorization filter
beer.filter( "/api/*", this::authorize );
beer.filter( "/api/admin/*", this::authorizeAdmin );
// FILES
beer.staticFiles( "/", "/static" );
// ENDPOINTS
beer.post( "/login", this::login );
beer.get( "/api/teams", ( req, res ) -> sqlConnector.executeQueryToList( "select * from v_teams" ) );
beer.get( "/api/scenarios", ( req, res ) -> sqlConnector.executeQueryToList( "select * from v_scenarios" ) );
beer.get( "/api/scenarios/:id", this::getScenario );
beer.post( "/api/scores", this::postScores );
// admin routes
beer.get( "/api/admin/total-scores", ( req, res ) -> sqlConnector.executeQueryToList( "select * from v_total_scores" ) );
beer.get( "/api/admin/teams-total-scores", ( req, res ) -> sqlConnector.executeQueryToList( "select * from v_teams_total_scores" ) );
beer.get( "/api/admin/teams", ( req, res ) -> sqlConnector.executeQueryToList( "select * from v_teams" ) );
beer.get( "/api/admin/scores", ( req, res ) -> sqlConnector.executeQueryToList( "select * from scores" ) );
beer.get( "/api/admin/managements", ( req, res ) -> sqlConnector.executeQueryToList( "select * from v_managements" ) );
beer.start( );
}
private void authorize( HttpServletRequest req, HttpServletResponse res ) throws Exception {
var authorizationHeader = req.getHeader( "Authorization" );
if ( authorizationHeader == null ) {
throw new BadRequestException( "No authorization header" );
}
var claims = BeerAuthUtils.verifyJwt( secretKey, authorizationHeader.replace( "Bearer ", "" ) );
var user = ( EmsRallyUser )
sqlConnector.executeQueryToObject(
"select * from users where id = ?",
Arrays.asList( claims.getSubject( ) ),
EmsRallyUser.class
);
var isUserInactive = user.getStatus( ) == 0;
if ( isUserInactive )
throw new ForbiddenException( "User is inactive" );
List roles = sqlConnector.executeQueryToList(
ROLES_QUERY,
Arrays.asList( user.getId( ) ), EmsRallyUserRole.class
);
user.setRoles( roles.stream( ).map( role -> role.getName( ) ).toList( ) );
BeerUtils.setReqUser( req, user );
}
private void authorizeAdmin( HttpServletRequest req, HttpServletResponse res ) throws Exception {
var user = ( EmsRallyUser ) BeerUtils.getReqUser( req );
var isAdmin = user.getRoles( ).contains( "Admin" );
if ( !isAdmin ) {
throw new ForbiddenException( "User is not admin" );
}
}
private EmsRallyUser login( HttpServletRequest req, HttpServletResponse res ) throws Exception {
var creds = BeerUtils.parseBasicAuthCredentials( req );
EmsRallyUser user = sqlConnector.executeQueryToObject(
"select * from users where username = ? and password = ?",
Arrays.asList(
creds.getUsername( ),
DigestUtils.sha1Hex( creds.getPassword( ) )
),
EmsRallyUser.class );
if ( user == null )
throw new UnauthorizedException( "User credentials are wrong!" );
List roles = sqlConnector.executeQueryToList(
ROLES_QUERY,
Arrays.asList( user.getId( ) ), EmsRallyUserRole.class
);
user.setRoles( roles.stream( ).map( role -> role.getName( ) ).toList( ) );
user.setToken( BeerAuthUtils.generateJwt( secretKey, 1440, user.getId( ).toString( ) ) );
return user;
}
private Scenario getScenario( HttpServletRequest req, HttpServletResponse res ) throws Exception {
var scenarioId = Integer.parseInt( BeerUtils.getPathParam( req, "id" ) );
Scenario scenario = sqlConnector.executeQueryToObject(
"""
select * from scenarios as s
where 1=1
and s.id = ?
""",
Arrays.asList( scenarioId ),
Scenario.class );
List managements =
sqlConnector.executeQueryToList(
"""
select m.*
from
managements_v2 as m,
scenario_has_managements as shm
where 1=1
and shm.management_id = m.id
and shm.scenario_id = ?
""",
Arrays.asList( scenarioId ),
Management.class );
scenario.setManagements( managements );
return scenario;
}
private SimpleMessage postScores( HttpServletRequest req, HttpServletResponse res ) throws Exception {
SaveScoresBody body = BeerUtils.parseBody( req, SaveScoresBody.class );
body.validate( );
var user = ( EmsRallyUser ) BeerUtils.getReqUser( req );
// generate a batch id from current timestamp in order
// to know which scores provided together
var batchId = DigestUtils.sha256Hex( LocalDateTime.now( ).toString( ) + user.getId( ).toString( ) );
for ( Score score: body.getScores( ) ) {
sqlConnector.executeUpdate(
"insert into scores_v2 ( batch_id, team_id, management_id, score, comment, evaluator_id ) values ( ?, ?, ?, ?, ?, ? )",
Arrays.asList(
batchId,
body.getTeamId( ),
score.getManagementId( ),
score.getScore( ),
score.getComment( ),
user.getId( ) )
);
}
sqlConnector.executeUpdate(
"insert into comments ( team_id, scenario_id, evaluator_id, comments ) values ( ?, ?, ?, ? )",
Arrays.asList(
body.getTeamId( ),
body.getScenarioId( ),
user.getId( ),
body.getComments( )
)
);
return new SimpleMessage( "Scenario has been submitted!" );
}
}