Skip to content

Commit

Permalink
default res.send function
Browse files Browse the repository at this point in the history
  • Loading branch information
codekeyz committed Jan 28, 2024
1 parent 0e6239d commit 488904a
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 127 deletions.
25 changes: 15 additions & 10 deletions packages/pharaoh/lib/src/http/response.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,32 @@ import 'message.dart';

part 'response_impl.dart';

final applicationOctetStreamType = ContentType('application', 'octet-stream');

abstract class Response extends Message<shelf.Body?> {
Response(super.body, {super.headers = const {}});

/// Constructs an HTTP Response
static Response create({int? statusCode, Object? body, Encoding? encoding, Map<String, dynamic>? headers}) =>
ResponseImpl._(
body: body == null ? null : Body(body),
ended: false,
statusCode: statusCode,
headers: headers ?? {},
);
static Response create({
int? statusCode,
Object? body,
Encoding? encoding,
Map<String, dynamic>? headers,
}) {
return ResponseImpl._(
body: body == null ? null : Body(body),
ended: false,
statusCode: statusCode,
headers: headers ?? {},
);
}

Response header(String headerKey, String headerValue);

/// Creates a new cookie setting the name and value.
///
/// [name] and [value] must be composed of valid characters according to RFC
/// 6265.
Response cookie(String name, Object? value, [CookieOpts opts = const CookieOpts()]);
Response cookie(String name, Object? value,
[CookieOpts opts = const CookieOpts()]);

Response withCookie(Cookie cookie);

Expand Down
61 changes: 31 additions & 30 deletions packages/pharaoh/lib/src/http/response_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -85,18 +85,22 @@ class ResponseImpl extends Response {
}

@override
ResponseImpl movedPermanently(String url) => redirect(url, HttpStatus.movedPermanently);
ResponseImpl movedPermanently(String url) =>
redirect(url, HttpStatus.movedPermanently);

@override
ResponseImpl notModified({Map<String, dynamic>? headers}) {
final existingHeaders = this.headers;
if (headers != null) headers.forEach((key, val) => existingHeaders[key] = val);
if (headers != null) {
headers.forEach((key, val) => existingHeaders[key] = val);
}

return ResponseImpl._(
ended: true,
statusCode: HttpStatus.notModified,
headers: existingHeaders
..removeWhere((name, _) => name.toLowerCase() == HttpHeaders.contentLengthHeader)
..removeWhere(
(name, _) => name.toLowerCase() == HttpHeaders.contentLengthHeader)
..[HttpHeaders.dateHeader] = formatHttpDate(DateTime.now()),
);
}
Expand Down Expand Up @@ -124,24 +128,29 @@ class ResponseImpl extends Response {
}

@override
ResponseImpl notFound([String? message]) => json(error(message ?? 'Not found'), statusCode: HttpStatus.notFound);
ResponseImpl notFound([String? message]) =>
json(error(message ?? 'Not found'), statusCode: HttpStatus.notFound);

@override
ResponseImpl unauthorized({Object? data}) => json(data ?? error('Unauthorized'), statusCode: HttpStatus.unauthorized);
ResponseImpl unauthorized({Object? data}) =>
json(data ?? error('Unauthorized'), statusCode: HttpStatus.unauthorized);

@override
ResponseImpl internalServerError([String? message]) =>
json(error(message ?? 'Internal Server Error'), statusCode: HttpStatus.internalServerError);
json(error(message ?? 'Internal Server Error'),
statusCode: HttpStatus.internalServerError);

@override
ResponseImpl ok([String? data]) => this.end()
..headers[HttpHeaders.contentTypeHeader] = ContentType.text.toString()
..body = shelf.Body(data, encoding);

@override
ResponseImpl send(Object data) => this.end()
..headers[HttpHeaders.contentTypeHeader] = _getContentType(data, valueWhenNull: ContentType.html).toString()
..body = shelf.Body(data);
ResponseImpl send(Object data) {
return this.end()
..headers[HttpHeaders.contentTypeHeader] ??= ContentType.binary.toString()
..body = shelf.Body(data);
}

@override
ResponseImpl end() => ResponseImpl._(
Expand All @@ -151,30 +160,21 @@ class ResponseImpl extends Response {
statusCode: statusCode,
);

ContentType _getContentType(Object data, {required ContentType valueWhenNull}) {
final isBuffer = _isBuffer(data);
final mType = mediaType;
if (mType == null) {
return isBuffer ? applicationOctetStreamType : valueWhenNull;
}

/// Always use charset :utf-8 unless
/// we have to deal with buffers.
final charset = isBuffer ? mType.parameters['charset'] : 'utf-8';
return ContentType.parse('${mType.mimeType}; charset=$charset');
}

/// TODO research on how to tell if an object is a buffer
bool _isBuffer(Object object) => object is! String;

@override
ResponseImpl format(Request request, Map<String, Function(ResponseImpl res)> options) {
ResponseImpl format(
Request request,
Map<String, Function(ResponseImpl res)> options,
) {
var reqAcceptType = request.headers[HttpHeaders.acceptHeader];
if (reqAcceptType is Iterable) reqAcceptType = reqAcceptType.join();

final handler = options[reqAcceptType] ?? options['_'];

if (handler == null) {
return json(error('Not Acceptable'), statusCode: HttpStatus.notAcceptable);
return json(
error('Not Acceptable'),
statusCode: HttpStatus.notAcceptable,
);
}

return handler.call(this);
Expand All @@ -198,7 +198,8 @@ class ResponseImpl extends Response {
..headers[HttpHeaders.setCookieHeader] = _cookies;

@override
Response render(String name, [Map<String, dynamic> data = const {}]) => this.end()
..viewToRender = ViewRenderData(name, data)
..headers[HttpHeaders.contentTypeHeader] = ContentType.html.toString();
Response render(String name, [Map<String, dynamic> data = const {}]) =>
this.end()
..viewToRender = ViewRenderData(name, data)
..headers[HttpHeaders.contentTypeHeader] = ContentType.html.toString();
}
20 changes: 11 additions & 9 deletions packages/pharaoh/test/http/res.format_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ void main() {
group('res.format(Map<String, Function(Response)> options)', () {
test('should respond using :accept provided', () async {
final app = Pharaoh()
..get(
'/',
(req, res) => res.format(req, {
ContentType.text.toString(): (res) => res.ok('Hello World'),
ContentType.html.toString(): (res) => res.send('<p>Hello World</p>'),
}),
);
..get('/', (req, res) {
return res.format(req, {
'text/plain; charset=utf-8': (res) => res.ok('Hello World'),
'text/html; charset=utf-8': (res) =>
res.type(ContentType.html).send('<p>Hello World</p>'),
});
});

await (await request<Pharaoh>(app))
.get(
Expand Down Expand Up @@ -42,7 +42,8 @@ void main() {
'/',
(req, res) => res.format(req, {
ContentType.text.toString(): (res) => res.ok('Hello World'),
ContentType.html.toString(): (res) => res.send('<p>Hello World</p>'),
ContentType.html.toString(): (res) =>
res.send('<p>Hello World</p>'),
'_': (res) => res.json({'message': 'Hello World'})
}),
);
Expand All @@ -61,7 +62,8 @@ void main() {
'/',
(req, res) => res.format(req, {
ContentType.text.toString(): (res) => res.ok('Hello World'),
ContentType.html.toString(): (res) => res.send('<p>Hello World</p>'),
ContentType.html.toString(): (res) =>
res.send('<p>Hello World</p>'),
}),
);

Expand Down
96 changes: 19 additions & 77 deletions packages/pharaoh/test/http/res.send_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,99 +7,41 @@ import 'package:spookie/spookie.dart';

void main() {
group('.send(Object)', () {
test('should send <String> send as html', () async {
test('should default content-Type to octet-stream', () async {
final app = Pharaoh();

app.use((req, res, next) {
next(res.send("<p>Hey</p>"));
final buffer = Uint8List.fromList(utf8.encode("Hello World"));
next(res.send(buffer));
});

await (await request<Pharaoh>(app))
.get('/')
.expectStatus(200)
.expectBody('<p>Hey</p>')
.expectContentType('text/html; charset=utf-8')
.expectBody('Hello World')
.expectContentType('application/octet-stream')
.test();
});

test('should not override previous Content-Types', () async {
final app = Pharaoh();

app.use((req, res, next) {
next(res.send("<p>Hey</p>"));
});

await (await request<Pharaoh>(app))
.get('/')
final app = Pharaoh()
..get('/html', (req, res) {
return res.type(ContentType.html).send("<p>Hey</p>");
})
..get('/text', (req, res) {
return res.type(ContentType.text).send("Hey");
});

final tester = await request<Pharaoh>(app);

await tester
.get('/html')
.expectContentType('text/html; charset=utf-8')
.expectStatus(200)
.expectBody('<p>Hey</p>')
.test();
});

test('should not override previous Content-Types', () async {
final app = Pharaoh();

app.use((req, res, next) {
next(res.type(ContentType.text).send("<p>Hey</p>"));
});

await (await request<Pharaoh>(app))
.get('/')
.expectContentType('text/plain; charset=utf-8')
.expectStatus(200)
.expectBody('<p>Hey</p>')
.test();
});

test('should override charset in Content-Type', () async {
final app = Pharaoh();

app.use((req, res, next) {
res = res.header('content-type', 'text/plain; charset=iso-8859-1');

next(res.send('Hey'));
});

await (await request<Pharaoh>(app))
.get('/')
.expectStatus(200)
await tester
.get('/text')
.expectContentType('text/plain; charset=utf-8')
.expectBody('Hey')
.test();
});

test('should keep charset in Content-Type for <Buffers>', () async {
final app = Pharaoh();

app.use((req, res, next) {
res = res.header('content-type', 'text/plain; charset=iso-8859-1');
final buffer = Uint8List.fromList(utf8.encode("Hello World"));

next(res.send(buffer));
});

await (await request<Pharaoh>(app))
.get('/')
.expectStatus(200)
.expectContentType('text/plain; charset=iso-8859-1')
.expectBody('Hello World')
.test();
});

test('should send <Buffer> as octet-stream', () async {
final app = Pharaoh();

app.use((req, res, next) {
final buffer = Uint8List.fromList(utf8.encode("Hello World"));
next(res.send(buffer));
});

await (await request<Pharaoh>(app))
.get('/')
.expectStatus(200)
.expectBody('Hello World')
.expectContentType('application/octet-stream')
.test();
});
});
Expand Down
5 changes: 4 additions & 1 deletion packages/pharaoh/test/http/res.type_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ void main() {
test('should set the Content-Type with type/subtype', () async {
final app = Pharaoh()
..get('/', (req, res) {
return res.type(ContentType.parse('application/vnd.amazon.ebook')).send('var name = "tj";');
final cType =
ContentType('application', 'vnd.amazon.ebook', charset: 'utf-8');

return res.type(cType).send('var name = "tj";');
});

await (await request<Pharaoh>(app))
Expand Down

0 comments on commit 488904a

Please sign in to comment.