Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release 0.4.0 #26

Merged
merged 41 commits into from
Jun 9, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
ea31463
chore: update readme
francescovallone Jun 2, 2024
af4a326
chore: change benchmark table build
francescovallone Jun 2, 2024
479e38f
chore: new benchmarks
francescovallone Jun 2, 2024
fef511f
chore: update readme
francescovallone Jun 2, 2024
5eae7ec
Merge branch 'develop' of https://github.com/francescovallone/serinus…
francescovallone Jun 2, 2024
874511d
feat(#14): add a first version of response events
francescovallone Jun 3, 2024
7f1da6a
feat(#14): add a first version of response events
francescovallone Jun 3, 2024
4122eed
Merge branch 'feat/14' of https://github.com/francescovallone/serinus…
francescovallone Jun 3, 2024
8d930a7
feat(#14): change response flow to use the events
francescovallone Jun 3, 2024
0dfc936
feat(#14): add test for response events
francescovallone Jun 5, 2024
cf9627b
feat(#18): initial implementation
francescovallone Jun 5, 2024
99ee290
feat(#14): export the response event enum, and remove unnecessary imp…
francescovallone Jun 5, 2024
ad8c755
Merge pull request #19 from francescovallone/feat/14
francescovallone Jun 5, 2024
4f89524
feat(#17): the file is now read as a stream and it is sent correctly …
francescovallone Jun 5, 2024
b981518
Merge pull request #20 from francescovallone/feat/17
francescovallone Jun 5, 2024
faaadf6
fix(#22): add further checks to 'get' method in controllers to preven…
francescovallone Jun 7, 2024
ddf0bbe
feat(#16): add factory constructors to Route and remove abstract modi…
francescovallone Jun 7, 2024
153d0de
docs(#15): add request_lifecycle and base path to docs
francescovallone Jun 7, 2024
ff5e9b4
fix(#18): remove execution context, simplify context creation
francescovallone Jun 7, 2024
c66cec0
feat(#18): initial implementation
francescovallone Jun 5, 2024
d1d4b92
fix(#18): remove execution context, simplify context creation
francescovallone Jun 7, 2024
8f7aee3
feat(#18)!: remove pipes
francescovallone Jun 7, 2024
8ad9644
test(#18): remove pipes reference from tests
francescovallone Jun 7, 2024
25535cc
feat(#18): finalize hooks, add transform and parse methods to route
francescovallone Jun 7, 2024
c47244d
feat(#18): finalize hooks, add transform and parse methods to route
francescovallone Jun 7, 2024
d5de627
feat(#18): add local version of before and after handle
francescovallone Jun 8, 2024
0f30768
docs(#15): start request lifecycle page
francescovallone Jun 8, 2024
f86225a
feat(#18)!: remove guards and middlewares
francescovallone Jun 8, 2024
409233b
chore: add dockerfile for documentation
francescovallone Jun 8, 2024
ebe56c8
fix: change vuepress to vitepress
francescovallone Jun 8, 2024
82d876d
feat(#18): re-add middlewares
francescovallone Jun 9, 2024
d7610dc
docs(#15): new homepage for serinus docs
francescovallone Jun 9, 2024
98e785d
feat(#18): re-enable middlewares tests and fix issue on request object
francescovallone Jun 9, 2024
694df9f
feat(#15): add correct lifecycle image
francescovallone Jun 9, 2024
3d809e4
Merge pull request #23 from francescovallone/docs/15
francescovallone Jun 9, 2024
42af888
docs(#16): add corresponding documentation
francescovallone Jun 9, 2024
4404949
Merge pull request #24 from francescovallone/feat/16
francescovallone Jun 9, 2024
7b69876
Merge branch 'develop' into feat/18
francescovallone Jun 9, 2024
8ba97a9
Merge pull request #25 from francescovallone/feat/18
francescovallone Jun 9, 2024
027234a
docs: remove guards file and guard reference in routes
francescovallone Jun 9, 2024
a028f69
chore: prepare for release (0.4.0)
francescovallone Jun 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat(#18): initial implementation
  • Loading branch information
francescovallone committed Jun 7, 2024
commit c66cec03a24ef34cecc11e7eed863171d209fa70
19 changes: 19 additions & 0 deletions packages/serinus/bin/bearer_hook.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import 'package:serinus/serinus.dart';
import 'package:serinus/src/services/hook.dart';

class BearerHook extends Hook {

const BearerHook();

@override
Future<void> beforeRequest(Request request, InternalResponse response) async {
String? authValue = request.headers['authorization'];
authValue = authValue?.toLowerCase();
if(authValue == null || !authValue.contains('bearer')) {
throw UnauthorizedException();
}
final String token = authValue.split('bearer ')[1];
request.addData('token', token);
}

}
106 changes: 106 additions & 0 deletions packages/serinus/bin/cors_hook.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import 'package:serinus/serinus.dart';
import 'package:serinus/src/services/hook.dart';

class CorsHook extends Hook<Response> {

final List<String> allowedOrigins;

CorsHook({this.allowedOrigins = const ['*']}){
_defaultHeaders = {
'Access-Control-Expose-Headers': '',
'Access-Control-Allow-Credentials': 'true',
'Access-Control-Allow-Headers': _defaultHeadersList.join(','),
'Access-Control-Allow-Methods': _defaultMethodsList.join(','),
'Access-Control-Max-Age': '86400',
};
_defaultHeadersAll =
_defaultHeaders.map((key, value) => MapEntry(key, [value]));
}

Map<String, List<String>> _defaultHeadersAll = {};

final _defaultHeadersList = [
'accept',
'accept-encoding',
'authorization',
'content-type',
'dnt',
'origin',
'user-agent',
'access-control-allow-origin'
];

final _defaultMethodsList = [
'DELETE',
'GET',
'OPTIONS',
'PATCH',
'POST',
'PUT'
];

Map<String, String> _defaultHeaders = {};

/// The response headers.
Map<String, String> responseHeaders = {};

@override
Future<Response?> beforeRequest(Request request, InternalResponse response) async {
if(request.method == 'OPTIONS') {
response.headers({
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
'Access-Control-Max-Age': '86400',
});
response.status(200);
await response.send();
return null;
}
return null;
}

@override
Future<Response?> onRequest(Request request, RequestContext? context, ReqResHandler? handler, InternalResponse response) async {
/// Get the origin from the request headers.
final origin = request.headers['origin'];

/// Check if the origin is allowed.
if (
context != null && handler != null && (
origin == null ||
(!allowedOrigins.contains('*') && !allowedOrigins.contains(origin))
)
) {
return handler(context);
}

/// Set the response headers.
final headers = <String, List<String>>{
..._defaultHeadersAll,
};

/// Add the origin to the response headers.
headers['Access-Control-Allow-Origin'] = [origin];

/// Stringify the headers.
final stringHeaders =
headers.map((key, value) => MapEntry(key, value.join(',')));
responseHeaders = {
...stringHeaders,
};

/// Call the handler.
final response = await handler!(context!);

/// Add the headers to the response.
response.addHeaders({
...stringHeaders,
});

/// Return the response.
return response;

}

}
3 changes: 3 additions & 0 deletions packages/serinus/bin/serinus.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'dart:io';

import 'package:serinus/serinus.dart';


class TestMiddleware extends Middleware {
int counter = 0;

Expand Down Expand Up @@ -239,6 +240,8 @@ void main(List<String> arguments) async {
// type: VersioningType.uri,
// version: 1
// );
//application.use(CorsHook());
//application.use(BearerHook());
application.changeBodySizeLimit(
BodySizeLimit.change(text: 10, size: BodySizeValue.b));
await application.serve();
Expand Down
9 changes: 4 additions & 5 deletions packages/serinus/lib/src/contexts/request_context.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ sealed class RequestContext {
final Request request;

/// The [body] property contains the body of the context.
late final Body body;
Body get body => request.body ?? Body.empty();

/// The [path] property contains the path of the request.
String get path => request.path;
Expand All @@ -32,7 +32,6 @@ sealed class RequestContext {
RequestContext(
this.providers,
this.request,
this.body,
);

/// This method is used to retrieve a provider from the context.
Expand All @@ -45,7 +44,7 @@ sealed class RequestContext {
}

class _RequestContextImpl extends RequestContext {
_RequestContextImpl(super.providers, super.request, super.body);
_RequestContextImpl(super.providers, super.request);

@override
T use<T>() {
Expand All @@ -69,8 +68,8 @@ final class RequestContextBuilder {
});

/// The [build] method is used to build the request context.
RequestContext build(Request request, Body body) {
_context = _RequestContextImpl(providers, request, body);
RequestContext build(Request request) {
_context = _RequestContextImpl(providers, request);
return _context!;
}
}
8 changes: 8 additions & 0 deletions packages/serinus/lib/src/core/application.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import '../http/http.dart';
import '../http/internal_request.dart';
import '../injector/explorer.dart';
import '../mixins/mixins.dart';
import '../services/hook.dart';
import '../services/logger_service.dart';
import '../versioning.dart';
import 'core.dart';
Expand Down Expand Up @@ -194,4 +195,11 @@ class SerinusApplication extends Application {
Future<void> register() async {
await modulesContainer.registerModules(entrypoint, entrypoint.runtimeType);
}

/// The [use] method is used to add a hook to the application.
void use(Hook hook) {
config.addHook(hook);
_logger.info('Hook ${hook.runtimeType} added to application');
}

}
9 changes: 9 additions & 0 deletions packages/serinus/lib/src/core/application_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import '../body_size_limit.dart';
import '../engines/view_engine.dart';
import '../global_prefix.dart';
import '../http/cors.dart';
import '../services/hook.dart';
import '../versioning.dart';

/// The configuration for the application
Expand Down Expand Up @@ -120,6 +121,14 @@ final class ApplicationConfig {
/// The ws adapter for the application
WsAdapter? wsAdapter;

/// The hooks for the application
final Set<Hook> hooks = {};

/// Add a hook to the application
void addHook(Hook hook) {
hooks.add(hook);
}

/// The application config constructor
ApplicationConfig({
required this.host,
Expand Down
8 changes: 2 additions & 6 deletions packages/serinus/lib/src/handlers/handler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,6 @@ abstract class Handler {
/// This method is responsible for handling the request.
Future<void> handle(
InternalRequest request, InternalResponse response) async {
if (request.method == 'OPTIONS') {
await config.cors?.call(request, Request(request), null, null);
return;
}
try {
await handleRequest(request, response);
} on SerinusException catch (e) {
Expand All @@ -46,10 +42,10 @@ abstract class Handler {

/// Build the request context from the request and body
RequestContext buildRequestContext(
Iterable<Provider> providers, Request request, Body body) {
Iterable<Provider> providers, Request request) {
RequestContextBuilder builder = RequestContextBuilder(providers: {
for (final provider in providers) provider.runtimeType: provider
});
return builder.build(request, body);
return builder.build(request);
}
}
43 changes: 27 additions & 16 deletions packages/serinus/lib/src/handlers/request_handler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,29 @@ class RequestHandler extends Handler {
@override
Future<void> handleRequest(
InternalRequest request, InternalResponse response) async {
final Request wrappedRequest = Request(request);
await wrappedRequest.parseBody();
final body = wrappedRequest.body!;
final bodySizeLimit = config.bodySizeLimit;
if (bodySizeLimit.isExceeded(body)) {
throw PayloadTooLargeException(
message: 'Request body size is too large',
uri: Uri.parse(request.path));
}
for(final hook in config.hooks) {
if(response.isClosed){
return;
}
await hook.beforeRequest(wrappedRequest, response);
}
Response? result;
final routeLookup = router.getRouteByPathAndMethod(
request.path.endsWith('/')
? request.path.substring(0, request.path.length - 1)
: request.path,
request.method.toHttpMethod());
final routeData = routeLookup.route;
wrappedRequest.setParams(params: routeLookup.params);
if (routeData == null) {
throw NotFoundException(
message:
Expand All @@ -63,19 +79,7 @@ class RequestHandler extends Handler {
}
final scopedProviders = (injectables.providers
.addAllIfAbsent(modulesContainer.globalProviders));
final wrappedRequest = Request(
request,
params: routeLookup.params,
);
await wrappedRequest.parseBody();
final body = wrappedRequest.body!;
final bodySizeLimit = config.bodySizeLimit;
if (bodySizeLimit.isExceeded(body)) {
throw PayloadTooLargeException(
message: 'Request body size is too large',
uri: Uri.parse(request.path));
}
final context = buildRequestContext(scopedProviders, wrappedRequest, body);
final context = buildRequestContext(scopedProviders, wrappedRequest);
final middlewares = injectables.filterMiddlewaresByRoute(
routeData.path, wrappedRequest.params);
ExecutionContext? executionContext;
Expand All @@ -97,14 +101,21 @@ class RequestHandler extends Handler {
executionContext = await handlePipes(route.pipes, controller.pipes,
injectables.pipes, context, executionContext);
}
if (config.cors != null) {
result =
await config.cors?.call(request, wrappedRequest, context, handler);
if (config.hooks.isNotEmpty) {
for (final hook in config.hooks) {
if (response.isClosed) {
return;
}
result = await hook.onRequest(wrappedRequest, context, handler, response);
}
} else {
result = await handler.call(context);
}
await response.finalize(result ?? Response.text(''),
viewEngine: config.viewEngine);
for(final hook in config.hooks) {
await hook.afterRequest(request, response);
}
}

/// Handles the middlewares
Expand Down
9 changes: 8 additions & 1 deletion packages/serinus/lib/src/http/internal_response.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ class InternalResponse {
/// The base url of the server
final String? baseUrl;

bool _isClosed = false;

/// This method is used to check if the response is closed.
bool get isClosed => _isClosed;

/// The [InternalResponse] constructor is used to create a new instance of the [InternalResponse] class.
InternalResponse(this._original, {this.baseUrl}) {
_original.headers.chunkedTransferEncoding = false;
Expand All @@ -37,12 +42,13 @@ class InternalResponse {
/// This method is used to send data to the response.
///
/// After sending the data, the response will be closed.
Future<void> send(List<int> data) async {
Future<void> send([List<int> data = const []]) async {
_original.add(data);
_events.add(ResponseEvent.data);
_original.close();
_events.add(ResponseEvent.afterSend);
_events.add(ResponseEvent.close);
_isClosed = true;
}

/// This method is used to send a stream of data to the response.
Expand All @@ -54,6 +60,7 @@ class InternalResponse {
_original.close();
_events.add(ResponseEvent.afterSend);
_events.add(ResponseEvent.close);
_isClosed = true;
}

/// This method is used to set the status code of the response.
Expand Down
7 changes: 7 additions & 0 deletions packages/serinus/lib/src/http/request.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ class Request {
}
}

/// This method is used to set the parameters of the request.
void setParams({
Map<String, dynamic> params = const {},
}) {
params.addAll(params);
}

final Map<String, dynamic> _queryParamters = {};

/// The path of the request.
Expand Down
Loading