Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
32 changes: 17 additions & 15 deletions lib/src/http.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ import 'router.dart';
import 'server.dart';
import 'validation_exception.dart';

final List<IsolateSupervisor> _supervisors = [];

/// Http class used to bootstrap your Http server
/// You need to use one of the server adapters. Currently only
/// Shelf adapter is available
Expand Down Expand Up @@ -50,6 +48,8 @@ class Http {
_router = Router();
}

final List<IsolateSupervisor> _supervisors = [];

List<IsolateSupervisor> get supervisors => _supervisors;

/// Server adapter, currently only shelf server is supported
Expand Down Expand Up @@ -318,7 +318,7 @@ class Http {

Future<void> executeGroupHooks() async {
for (final group in groups) {
for (final hook in _init) {
for (final hook in hooks) {
if (hook.getGroups().contains(group)) {
final arguments = await argsCallback.call(hook);
Function.apply(
Expand Down Expand Up @@ -440,19 +440,20 @@ class Http {
}
}

Response response;
if (route != null) {
if (isDevelopment) {
print('[UtopiaHttp] Executing route: ${route.path}');
}
return execute(route, request, context);
response = await execute(route, request, context);
} else if (method == Request.options) {
if (isDevelopment) {
print(
'[UtopiaHttp] Handling OPTIONS request for path: ${request.url.path}',
);
}
try {
_executeHooks(
await _executeHooks(
_options,
groups,
(hook) async => _getArguments(
Expand All @@ -463,7 +464,7 @@ class Http {
globalHook: true,
globalHooksFirst: false,
);
return getResource<Response>('response', context: context);
response = getResource<Response>('response', context: context);
} on Exception catch (e) {
for (final hook in _errors) {
_di.set('error', () => e);
Expand All @@ -477,16 +478,17 @@ class Http {
);
}
}
return getResource<Response>('response', context: context);
response = getResource<Response>('response', context: context);
}
} else {
response = getResource<Response>('response', context: context);
response.text('Not Found');
response.status = 404;
if (isDevelopment) {
print(
'[UtopiaHttp] Responding with 404 Not Found for path: ${request.url.path}',
);
}
}
final response = getResource<Response>('response', context: context);
response.text('Not Found');
response.status = 404;
if (isDevelopment) {
print(
'[UtopiaHttp] Responding with 404 Not Found for path: ${request.url.path}',
);
}

// for each run, resources should be re-generated from callbacks
Expand Down
6 changes: 4 additions & 2 deletions lib/src/request.dart
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,10 @@ class Request {
}

String? _extractBoundary() {
if (!headers.containsKey('Content-Type')) return null;
final contentType = MediaType.parse(headers['Content-Type']!);
final ctHeader =
headers['Content-Type'] ?? headers['content-type'];
if (ctHeader == null) return null;
final contentType = MediaType.parse(ctHeader);
if (contentType.type != 'multipart') return null;

return contentType.parameters['boundary'];
Expand Down
1 change: 1 addition & 0 deletions lib/src/response.dart
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ class Response {
/// Set json response
void json(Map<String, dynamic> data, {int status = HttpStatus.ok}) {
contentType = ContentType.json;
this.status = status;
body = jsonEncode(data);
}

Expand Down
2 changes: 2 additions & 0 deletions lib/src/router.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class Router {
'PUT': {},
'PATCH': {},
'DELETE': {},
'HEAD': {},
};

List<int> _params = [];
Expand Down Expand Up @@ -128,6 +129,7 @@ class Router {
'PUT': {},
'PATCH': {},
'DELETE': {},
'HEAD': {},
};
}
}
69 changes: 60 additions & 9 deletions test/unit/http_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@ import 'package:utopia_di/utopia_validators.dart';
import 'package:utopia_http/utopia_http.dart';

void main() async {
final http = Http(ShelfServer('localhost', 8080));
http.setResource('rand', () => Random().nextInt(100));
http.setResource(
'first',
(String second) => 'first-$second',
injections: ['second'],
);
http.setResource('second', () => 'second');

group('Http', () {
test('resource injection', () async {
final http = Http(ShelfServer('localhost', 8080));
http.setResource('rand', () => Random().nextInt(100));
http.setResource(
'first',
(String second) => 'first-$second',
injections: ['second'],
);
http.setResource('second', () => 'second');

final resource = http.getResource('rand');

final route = Route('GET', '/path');
Expand Down Expand Up @@ -44,5 +44,56 @@ void main() async {
);
expect(res.body, 'x-def-y-def-$resource');
});

test('shutdown hooks execute for groups', () async {
final http = Http(ShelfServer('localhost', 8081));
var shutdownCalled = false;

http
.shutdown()
.groups(['api'])
.inject('response')
.action((Response response) {
shutdownCalled = true;
});

final route = Route('GET', '/test-shutdown');
route
.groups(['api'])
.inject('response')
.action((Response response) {
response.text('ok');
return response;
});

http.setResource('response', () => Response(''), context: 'test');
await http.execute(
route,
Request('GET', Uri.parse('/test-shutdown')),
'test',
);
expect(shutdownCalled, isTrue);
});

test('404 response for unmatched route', () async {
final http = Http(ShelfServer('localhost', 8082));
final response = await http.run(
Request('GET', Uri.parse('/nonexistent')),
'test-404',
);
expect(response.status, 404);
expect(response.body, 'Not Found');
});

test('supervisors are instance-level', () {
final http1 = Http(ShelfServer('localhost', 8083));
final http2 = Http(ShelfServer('localhost', 8084));
expect(identical(http1.supervisors, http2.supervisors), isFalse);
expect(http1.supervisors, isEmpty);
expect(http2.supervisors, isEmpty);
// Verify that modifying one instance's supervisors doesn't affect the other
expect(http1.supervisors.length, 0);
expect(http2.supervisors.length, 0);
});
});
}
61 changes: 61 additions & 0 deletions test/unit/response_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import 'dart:io';

import 'package:test/test.dart';
import 'package:utopia_http/utopia_http.dart';

void main() {
group('Response', () {
test('json sets status', () {
final response = Response('');
response.json({'key': 'value'}, status: HttpStatus.created);
expect(response.status, HttpStatus.created);
expect(response.body, '{"key":"value"}');
expect(response.contentType, ContentType.json);
});

test('json defaults to 200', () {
final response = Response('');
response.json({'key': 'value'});
expect(response.status, HttpStatus.ok);
});

test('text sets status', () {
final response = Response('');
response.text('hello', status: HttpStatus.accepted);
expect(response.status, HttpStatus.accepted);
expect(response.body, 'hello');
});

test('html sets status', () {
final response = Response('');
response.html('<p>hi</p>', status: HttpStatus.accepted);
expect(response.status, HttpStatus.accepted);
expect(response.body, '<p>hi</p>');
expect(response.contentType, ContentType.html);
});

test('noContent sets status and empty body', () {
final response = Response('data');
response.noContent();
expect(response.status, HttpStatus.noContent);
expect(response.body, '');
});

test('addHeader and removeHeader', () {
final response = Response('');
response.addHeader('X-Custom', 'value');
expect(response.headers['X-Custom'], 'value');
response.removeHeader('X-Custom');
expect(response.headers.containsKey('X-Custom'), false);
});

test('addCookie and removeCookie', () {
final response = Response('');
final cookie = Cookie('name', 'value');
response.addCookie(cookie);
expect(response.cookies.length, 1);
response.removeCookie(cookie);
expect(response.cookies.length, 0);
});
});
}