change body parsing

This commit is contained in:
Tobe O 2018-12-08 23:18:31 -05:00
parent c20c25fc04
commit d3c2192042
6 changed files with 114 additions and 81 deletions

View file

@ -4,5 +4,4 @@ library angel_framework;
export 'package:angel_http_exception/angel_http_exception.dart';
export 'package:angel_model/angel_model.dart';
export 'package:angel_route/angel_route.dart';
export 'package:body_parser/body_parser.dart' show FileUploadInfo;
export 'src/core/core.dart';

View file

@ -1,12 +1,14 @@
library angel_framework.http.request_context;
import 'dart:async';
import 'dart:convert';
import 'dart:io' show Cookie, HttpHeaders, HttpSession, InternetAddress;
import 'package:angel_container/angel_container.dart';
import 'package:body_parser/body_parser.dart';
import 'package:http_parser/http_parser.dart';
import 'package:http_server/http_server.dart';
import 'package:meta/meta.dart';
import 'package:mime/mime.dart';
import 'package:path/path.dart' as p;
import 'metadata.dart';
@ -19,9 +21,12 @@ part 'injection.dart';
/// A convenience wrapper around an incoming [RawRequest].
abstract class RequestContext<RawRequest> {
String _acceptHeaderCache, _extensionCache;
bool _acceptsAllCache;
BodyParseResult _body;
Map _provisionalQuery;
bool _acceptsAllCache, _hasParsedBody;
Map<String, dynamic> _bodyFields, _queryParameters;
List _bodyList;
Object _bodyObject;
List<HttpMultipartFormData> _bodyFiles;
MediaType _contentType;
/// The underlying [RawRequest] provided by the driver.
RawRequest get rawRequest;
@ -60,7 +65,7 @@ abstract class RequestContext<RawRequest> {
/// The content type of an incoming request.
MediaType get contentType =>
new MediaType.parse(headers.contentType.toString());
_contentType ??= new MediaType.parse(headers.contentType.toString());
/// The URL parameters extracted from the request URI.
Map<String, dynamic> params = <String, dynamic>{};
@ -83,6 +88,64 @@ abstract class RequestContext<RawRequest> {
/// The [Uri] instance representing the path this request is responding to.
Uri get uri;
/// The [Stream] of incoming binary data sent from the client.
Stream<List<int>> get body;
/// Returns `true` if [parseBody] has been called so far.
bool get hasParsedBody => _hasParsedBody;
/// Returns a *mutable* [Map] of the fields parsed from the request [body].
///
/// Note that [parseBody] must be called first.
Map<String, dynamic> get bodyFields {
if (!hasParsedBody) {
throw new StateError('The request body has not been parsed yet.');
} else if (_bodyFields == null) {
throw new StateError('The request body, $_bodyObject, is not a Map.');
}
return _bodyFields;
}
/// Returns a *mutable* [List] parsed from the request [body].
///
/// Note that [parseBody] must be called first.
List get bodyList {
if (!hasParsedBody) {
throw new StateError('The request body has not been parsed yet.');
} else if (_bodyList == null) {
throw new StateError('The request body, $_bodyObject, is not a List.');
}
return _bodyList;
}
/// Returns the parsed request body, whatever it may be (typically a [Map] or [List]).
///
/// Note that [parseBody] must be called first.
Object get bodyObject {
if (!hasParsedBody) {
throw new StateError('The request body has not been parsed yet.');
}
return _bodyObject;
}
/// Returns a *mutable* map of the files parsed from the request [body].
///
/// Note that [parseBody] must be called first.
List<HttpMultipartFormData> get bodyFiles {
if (!hasParsedBody) {
throw new StateError('The request body has not been parsed yet.');
}
return _bodyFiles;
}
/// Returns a *mutable* map of the fields contained in the query.
Map<String, dynamic> get queryParameters =>
_queryParameters ??= new Map<String, dynamic>.from(uri.queryParameters);
/// Returns the file extension of the requested path, if any.
///
/// Includes the leading `.`, if there is one.
@ -120,49 +183,54 @@ abstract class RequestContext<RawRequest> {
/// Returns as `true` if the client's `Accept` header indicates that it will accept any response content type.
bool get acceptsAll => _acceptsAllCache ??= accepts('*/*');
/// Retrieves the request body if it has already been parsed, or lazy-parses it before returning the body.
Future<Map> parseBody() => parse().then((b) => b.body);
/// Retrieves a list of all uploaded files if it has already been parsed, or lazy-parses it before returning the files.
Future<List<FileUploadInfo>> parseUploadedFiles() =>
parse().then((b) => b.files);
/// Retrieves the original request buffer if it has already been parsed, or lazy-parses it before returning the buffer..
///
/// This will return an empty `List` if you have not enabled `keepRawRequestBuffers` on your [Angel] instance.
Future<List<int>> parseRawRequestBuffer() =>
parse().then((b) => b.originalBuffer);
/// Retrieves the request body if it has already been parsed, or lazy-parses it before returning the query.
///
/// If [forceParse] is not `true`, then [uri].query will be returned, and no parsing will be performed.
Future<Map<String, dynamic>> parseQuery({bool forceParse: false}) {
if (_body == null && forceParse != true)
return new Future.value(
new Map<String, dynamic>.from(uri.queryParameters));
else
return parse().then((b) => b.query);
}
/// Manually parses the request body, if it has not already been parsed.
Future<BodyParseResult> parse() {
if (_body != null)
return new Future.value(_body);
else
_provisionalQuery = null;
return parseOnce().then((body) => _body = body);
}
Future<void> parseBody({Encoding encoding: utf8}) async {
if (!_hasParsedBody) {
_hasParsedBody = true;
/// Override this method to one-time parse an incoming request.
@virtual
Future<BodyParseResult> parseOnce();
if (contentType.type == 'application' && contentType.subtype == 'json') {
_bodyFiles = [];
var parsed = _bodyObject =
await body.transform(encoding.decoder).join().then(json.decode);
if (parsed is Map) {
_bodyFields = new Map<String, dynamic>.from(parsed);
} else if (parsed is List) {
_bodyList = parsed;
}
} else if (contentType.type == 'multipart' &&
contentType.subtype == 'form-data' &&
contentType.parameters.containsKey('boundary')) {
var boundary = contentType.parameters['boundary'];
var transformer = new MimeMultipartTransformer(boundary);
var parts = body.transform(transformer).map((part) =>
HttpMultipartFormData.parse(part, defaultEncoding: encoding));
_bodyFields = {};
_bodyFiles = [];
await for (var part in parts) {
if (part.isBinary) {
_bodyFiles.add(part);
} else if (part.isText &&
part.contentDisposition.parameters.containsKey('name')) {
// If there is no name, then don't parse it.
var key = part.contentDisposition.parameters['name'];
var value = await part.join();
_bodyFields[key] = value;
}
}
} else {
_bodyFields = {};
_bodyFiles = [];
}
}
}
/// Disposes of all resources.
Future close() {
_body = null;
_acceptsAllCache = null;
_acceptHeaderCache = null;
_provisionalQuery?.clear();
serviceParams.clear();
params.clear();
return new Future.value();

View file

@ -119,10 +119,6 @@ class Angel extends Routable {
/// or use `lazyBody()`.
bool eagerParseRequestBodies = false;
/// When set to `true`, the original body bytes will be stored
/// on requests. `false` by default.
bool keepRawRequestBuffers = false;
/// A function that renders views.
///
/// Called by [ResponseContext]@`render`.
@ -364,7 +360,6 @@ class Angel extends Routable {
this.logger,
this.eagerParseRequestBodies: false,
this.allowMethodOverrides: true,
this.keepRawRequestBuffers: false,
this.serializer,
this.viewGenerator})
: super(reflector) {

View file

@ -1,29 +1,10 @@
/// A basic server that prints "Hello, world!"
library performance.hello;
import 'dart:async';
import 'dart:io';
import 'dart:isolate';
import 'package:angel_container/mirrors.dart';
import 'package:angel_framework/angel_framework.dart';
import 'package:angel_framework/http.dart';
main() async {
var isolates = <Isolate>[];
for (int i = 0; i < Platform.numberOfProcessors; i++) {
isolates.add(await Isolate.spawn(start, i + 1));
}
await Future.wait(isolates.map((i) {
var rcv = new ReceivePort();
i.addOnExitListener(rcv.sendPort);
return rcv.first;
}));
//start(0);
}
void start(int id) {
var app = new Angel();
var http = new AngelHttp.custom(app, startShared, useZone: false);
@ -37,8 +18,6 @@ void start(int id) {
return oldHandler(e, req, res);
};
http.startServer('127.0.0.1', 3000).then((server) {
print(
'Instance #$id listening at http://${server.address.address}:${server.port}');
});
await http.startServer('127.0.0.1', 3000);
print('Listening at ${http.uri}');
}

View file

@ -2,18 +2,10 @@
library performance.hello;
import 'dart:io';
import 'dart:isolate';
main() {
for (int i = 0; i < Platform.numberOfProcessors - 1; i++)
Isolate.spawn(start, i + 1);
start(0);
}
void start(int id) {
HttpServer.bind('127.0.0.1', 3000, shared: true).then((server) {
print(
'Instance #$id listening at http://${server.address.address}:${server.port}');
return HttpServer.bind('127.0.0.1', 3000, shared: true).then((server) {
print('Listening at http://${server.address.address}:${server.port}');
server.listen((request) {
if (request.uri.path == '/') {

View file

@ -10,11 +10,11 @@ dependencies:
angel_http_exception: ^1.0.0
angel_model: ^1.0.0
angel_route: ^3.0.0
body_parser: ^1.0.0
charcode: ^1.0.0
combinator: ^1.0.0
file: ^5.0.0
http_parser: ^3.0.0
http_server: ^0.9.0
http2: ">=0.1.7 <2.0.0"
logging: ">=0.11.3 <1.0.0"
matcher: ^0.12.0