change body parsing
This commit is contained in:
parent
c20c25fc04
commit
d3c2192042
6 changed files with 114 additions and 81 deletions
|
@ -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';
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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}');
|
||||
}
|
||||
|
|
|
@ -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 == '/') {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue