π JetLeaf Web β HTTP Server & Web Framework
A comprehensive web framework for building HTTP servers, RESTful APIs, and dynamic web applications with JetLeaf.
π Overview
jetleaf_web provides everything needed to build production-grade web applications:
- HTTP Server β Multi-threaded server with keep-alive and compression
- RESTful Routing β Declarative route definition with
@RestControllerand HTTP method annotations - Request/Response Handling β Type-safe HTTP message processing
- Content Negotiation β Automatic format selection (JSON, XML, YAML, Form data)
- HTTP Message Converters β Built-in converters for common types
- Multipart File Uploads β Stream-based file handling
- Exception Handling β Centralized error resolution
- Session Management β HTTP session support with pluggable storage
- Template Rendering β Integration with JTL template engine
- CORS & Security β Cross-origin and CSRF protection
π Quick Start
Installation
Add jetleaf_web to your pubspec.yaml:
dependencies:
jetleaf_core: ^1.0.0
jetleaf_web: ^1.0.0
Basic Web Application
import 'package:jetleaf_core/core.dart';
import 'package:jetleaf_web/jetleaf_web.dart';
// Define a REST controller
@RestController('/api/products')
class ProductController {
final ProductService _service;
@Autowired
ProductController(this._service);
// GET /api/products
@GetMapping('/')
Future<HttpResponse> listProducts() async {
final products = await _service.getAllProducts();
return HttpResponse.ok(products);
}
// GET /api/products/:id
@GetMapping('/:id')
Future<HttpResponse> getProduct(
@PathVariable String id,
HttpRequest request,
) async {
final product = await _service.getProductById(id);
if (product == null) {
return HttpResponse.notFound();
}
return HttpResponse.ok(product);
}
// POST /api/products
@PostMapping('/')
Future<HttpResponse> createProduct(
@RequestBody Product product,
HttpRequest request,
) async {
final created = await _service.createProduct(product);
return HttpResponse.created(created);
}
// PUT /api/products/:id
@PutMapping('/:id')
Future<HttpResponse> updateProduct(
@PathVariable String id,
@RequestBody Product product,
HttpRequest request,
) async {
final updated = await _service.updateProduct(id, product);
if (updated == null) {
return HttpResponse.notFound();
}
return HttpResponse.ok(updated);
}
// DELETE /api/products/:id
@DeleteMapping('/:id')
Future<HttpResponse> deleteProduct(
@PathVariable String id,
HttpRequest request,
) async {
await _service.deleteProduct(id);
return HttpResponse.noContent();
}
}
// Start the application
void main() async {
final context = AnnotationConfigApplicationContext(['package:myapp']);
final server = context.getPod<WebServer>();
await server.start(port: 8080);
print('π Server running on http://localhost:8080');
}
ποΈ Architecture
Request/Response Pipeline
HTTP Request
β
Content Negotiation (Accept header)
β
Route Matching
β
Method Argument Resolution
β
Controller Method Execution
β
Return Value Handling
β
HTTP Message Conversion
β
HTTP Response
Key Components
WebServer
βββ RequestDispatcher
βββ HandlerAdapter (method invocation)
βββ ArgumentResolver (parameter injection)
βββ ReturnValueHandler (response creation)
βββ ContentNegotiationResolver
βββ HttpMessageConverterRegistry
βββ ExceptionResolver
π Key Features
1. Request Mapping
Path-based routing:
@RestController('/api/users')
class UserController {
// GET /api/users
@GetMapping('/')
Future<HttpResponse> list() { }
// GET /api/users/123
@GetMapping('/:id')
Future<HttpResponse> getById(@PathVariable String id) { }
// POST /api/users
@PostMapping('/')
Future<HttpResponse> create(@RequestBody User user) { }
// PUT /api/users/123
@PutMapping('/:id')
Future<HttpResponse> update(
@PathVariable String id,
@RequestBody User user,
) { }
// DELETE /api/users/123
@DeleteMapping('/:id')
Future<HttpResponse> delete(@PathVariable String id) { }
}
2. Parameter Resolution
Extract data from requests:
@RestController('/api')
class DataController {
// Path variables
@GetMapping('/users/:userId/posts/:postId')
Future<HttpResponse> getPost(
@PathVariable String userId,
@PathVariable String postId,
) { }
// Query parameters
@GetMapping('/search')
Future<HttpResponse> search(
@RequestParam String? query,
@RequestParam int page = 1,
@RequestParam int size = 10,
) { }
// Request body
@PostMapping('/create')
Future<HttpResponse> create(@RequestBody Map<String, dynamic> data) { }
// Request headers
@GetMapping('/info')
Future<HttpResponse> getInfo(
@RequestHeader String? authorization,
@RequestHeader('x-api-key') String? apiKey,
) { }
// HTTP session
@GetMapping('/profile')
Future<HttpResponse> getProfile(HttpSession session) { }
}
3. Content Negotiation
Automatic format selection:
@RestController('/api/data')
class DataController {
@GetMapping('/items')
Future<HttpResponse> getItems(HttpRequest request) async {
final items = [
{'id': 1, 'name': 'Item 1'},
{'id': 2, 'name': 'Item 2'},
];
// Returns JSON if Accept: application/json
// Returns XML if Accept: application/xml
// Returns YAML if Accept: application/yaml
return HttpResponse.ok(items);
}
}
4. HTTP Message Converters
Custom converters:
@Configuration()
class ConverterConfiguration {
@Pod()
HttpMessageConverterRegistry createRegistry() {
final registry = HttpMessageConverterRegistry();
// JSON converter
registry.register(JetsonHttpMessageConverter());
// XML converter
registry.register(JetsonXmlHttpMessageConverter());
// Form data converter
registry.register(FormHttpMessageConverter());
return registry;
}
}
5. Exception Handling
Centralized error handling:
@RestControllerAdvice()
class GlobalExceptionHandler {
@ExceptionHandler(NotFoundException)
HttpResponse handleNotFound(NotFoundException ex) {
return HttpResponse.notFound(
{'error': ex.message},
);
}
@ExceptionHandler(ValidationException)
HttpResponse handleValidation(ValidationException ex) {
return HttpResponse.badRequest(
{'error': 'Validation failed', 'details': ex.details},
);
}
@ExceptionHandler(Exception)
HttpResponse handleGeneric(Exception ex) {
return HttpResponse.internalServerError(
{'error': 'Internal server error'},
);
}
}
6. Multipart File Uploads
Handle file uploads:
@RestController('/api/files')
class FileController {
@PostMapping('/upload')
Future<HttpResponse> uploadFile(HttpRequest request) async {
// Parse multipart request
final parts = await request.getMultipartRequest().getParts();
for (final part in parts) {
if (part.isFile) {
final filename = part.filename;
final contentType = part.contentType;
final bytes = await part.readBytes();
// Process file
await saveFile(filename, bytes);
}
}
return HttpResponse.ok({'status': 'uploaded'});
}
}
7. HTTP Sessions
Session management:
@RestController('/api')
class SessionController {
@PostMapping('/login')
Future<HttpResponse> login(
@RequestBody LoginRequest req,
HttpSession session,
) async {
final user = await authenticateUser(req.email, req.password);
if (user == null) {
return HttpResponse.unauthorized();
}
session.setAttribute('user_id', user.id);
session.setAttribute('user_email', user.email);
return HttpResponse.ok(user);
}
@GetMapping('/profile')
Future<HttpResponse> getProfile(HttpSession session) async {
final userId = session.getAttribute('user_id');
if (userId == null) {
return HttpResponse.unauthorized();
}
final user = await getUserById(userId);
return HttpResponse.ok(user);
}
@PostMapping('/logout')
Future<HttpResponse> logout(HttpSession session) async {
session.invalidate();
return HttpResponse.ok({'status': 'logged out'});
}
}
8. Template Rendering
Render HTML with JTL:
@RestController('/pages')
class PageController {
final TemplateEngine _template;
@Autowired
PageController(this._template);
@GetMapping('/home')
Future<HttpResponse> homePage() async {
final html = await _template.render(
'pages/home.jtl',
{'title': 'Welcome', 'user': 'John'},
);
return HttpResponse.ok(html)
.header('Content-Type', 'text/html; charset=utf-8');
}
}
π― Common Patterns
Pattern 1: RESTful API Endpoint
@RestController('/api/users')
class UserController {
final UserService _service;
@Autowired
UserController(this._service);
@GetMapping('/')
Future<HttpResponse> listUsers() async {
final users = await _service.getAllUsers();
return HttpResponse.ok(users);
}
@PostMapping('/')
Future<HttpResponse> createUser(@RequestBody User user) async {
final created = await _service.createUser(user);
return HttpResponse.created(created);
}
}
Pattern 2: Error Handling with Status Codes
@GetMapping('/items/:id')
Future<HttpResponse> getItem(@PathVariable String id) async {
final item = await _service.getItemById(id);
if (item == null) {
return HttpResponse.notFound();
}
return HttpResponse.ok(item);
}
Pattern 3: Query Parameter Filtering
@GetMapping('/search')
Future<HttpResponse> search(
@RequestParam String? q,
@RequestParam int limit = 50,
) async {
if (q == null || q.isEmpty) {
return HttpResponse.badRequest(
{'error': 'Query parameter required'},
);
}
final results = await _service.search(q, limit: limit);
return HttpResponse.ok(results);
}
π HTTP Status Codes
Common HTTP response status codes:
| Code | Method | Use Case |
|---|---|---|
| 200 | ok() |
Successful GET/PUT |
| 201 | created() |
POST created resource |
| 204 | noContent() |
DELETE successful |
| 400 | badRequest() |
Invalid input |
| 401 | unauthorized() |
Missing authentication |
| 403 | forbidden() |
Insufficient permissions |
| 404 | notFound() |
Resource not found |
| 409 | conflict() |
Resource conflict |
| 500 | internalServerError() |
Server error |
β οΈ Common Issues
| Issue | Cause | Solution |
|---|---|---|
| Route not matched | Wrong mapping path | Verify @RestController base path and method annotation |
| 400 Bad Request | Invalid request body | Ensure request body matches pod type |
| 404 Not Found | Route not registered | Check controller is in package scan paths |
| Content Negotiation fails | Missing converter | Register converter in pod factory |
π Best Practices
β DO
- Use RESTful URL patterns (
GET /users,POST /users,PUT /users/:id) - Return appropriate HTTP status codes
- Use
@PathVariablefor resource IDs - Use
@RequestParamfor query/search parameters - Use
@RequestBodyfor form data - Implement global exception handling
- Validate input before processing
- Document API endpoints with comments
β DON'T
- Use
GETfor mutations (usePOST,PUT,DELETE) - Return
200 OKfor errors - Ignore content negotiation
- Forget to close multipart streams
- Share mutable state between requests
- Block the event loop in handlers
- Trust user input without validation
π¦ Dependencies
jetleaf_coreβ Core frameworkjetleaf_langβ Language utilitiesjetsonβ JSON serializationjtlβ Template renderingjetleaf_loggingβ Structured loggingjetleaf_envβ Configuration
π License
This package is part of the JetLeaf Framework. See LICENSE in the root directory.
π Related Packages
jetsonβ Object mapping and serializationjtlβ Template enginejetleaf_validationβ Data validationjetleaf_securityβ Security utilities
π Support
For issues, questions, or contributions, visit:
Created with β€οΈ by Hapnium
Libraries
- annotation
- Jetleaf Annotation sub-library
- config
- Jetleaf Auto-Configuration sub-library
- context
- Jetleaf Context sub-library
- converter
- Jetleaf HTTP Message Converter sub-library
- cors
- Jetleaf CORS sub-library
- csrf
- Jetleaf CSRF sub-library
- env
- Jetleaf Environment sub-library
- exception
- Jetleaf Exception sub-library
- http
- Jetleaf HTTP sub-library
- io
- Jetleaf IO sub-library
- jetleaf_web
- π JetLeaf Web Framework
- jetson
- π JetLeaf Jetson Object Mapping & Serialization Library
- jtl
- πΏ JetLeaf Template Engine (JTL)
- path
- Jetleaf Path sub-library
- rest
- Jetleaf REST sub-library
- server
- Jetleaf Web Server sub-library
- utils
- Jetleaf Web Utilities sub-library
- web
- Jetleaf Web sub-library