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_http_exception/angel_http_exception.dart';
|
||||||
export 'package:angel_model/angel_model.dart';
|
export 'package:angel_model/angel_model.dart';
|
||||||
export 'package:angel_route/angel_route.dart';
|
export 'package:angel_route/angel_route.dart';
|
||||||
export 'package:body_parser/body_parser.dart' show FileUploadInfo;
|
|
||||||
export 'src/core/core.dart';
|
export 'src/core/core.dart';
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
library angel_framework.http.request_context;
|
library angel_framework.http.request_context;
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
import 'dart:io' show Cookie, HttpHeaders, HttpSession, InternetAddress;
|
import 'dart:io' show Cookie, HttpHeaders, HttpSession, InternetAddress;
|
||||||
|
|
||||||
import 'package:angel_container/angel_container.dart';
|
import 'package:angel_container/angel_container.dart';
|
||||||
import 'package:body_parser/body_parser.dart';
|
|
||||||
import 'package:http_parser/http_parser.dart';
|
import 'package:http_parser/http_parser.dart';
|
||||||
|
import 'package:http_server/http_server.dart';
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
|
import 'package:mime/mime.dart';
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
|
|
||||||
import 'metadata.dart';
|
import 'metadata.dart';
|
||||||
|
@ -19,9 +21,12 @@ part 'injection.dart';
|
||||||
/// A convenience wrapper around an incoming [RawRequest].
|
/// A convenience wrapper around an incoming [RawRequest].
|
||||||
abstract class RequestContext<RawRequest> {
|
abstract class RequestContext<RawRequest> {
|
||||||
String _acceptHeaderCache, _extensionCache;
|
String _acceptHeaderCache, _extensionCache;
|
||||||
bool _acceptsAllCache;
|
bool _acceptsAllCache, _hasParsedBody;
|
||||||
BodyParseResult _body;
|
Map<String, dynamic> _bodyFields, _queryParameters;
|
||||||
Map _provisionalQuery;
|
List _bodyList;
|
||||||
|
Object _bodyObject;
|
||||||
|
List<HttpMultipartFormData> _bodyFiles;
|
||||||
|
MediaType _contentType;
|
||||||
|
|
||||||
/// The underlying [RawRequest] provided by the driver.
|
/// The underlying [RawRequest] provided by the driver.
|
||||||
RawRequest get rawRequest;
|
RawRequest get rawRequest;
|
||||||
|
@ -60,7 +65,7 @@ abstract class RequestContext<RawRequest> {
|
||||||
|
|
||||||
/// The content type of an incoming request.
|
/// The content type of an incoming request.
|
||||||
MediaType get contentType =>
|
MediaType get contentType =>
|
||||||
new MediaType.parse(headers.contentType.toString());
|
_contentType ??= new MediaType.parse(headers.contentType.toString());
|
||||||
|
|
||||||
/// The URL parameters extracted from the request URI.
|
/// The URL parameters extracted from the request URI.
|
||||||
Map<String, dynamic> params = <String, dynamic>{};
|
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.
|
/// The [Uri] instance representing the path this request is responding to.
|
||||||
Uri get uri;
|
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.
|
/// Returns the file extension of the requested path, if any.
|
||||||
///
|
///
|
||||||
/// Includes the leading `.`, if there is one.
|
/// 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.
|
/// Returns as `true` if the client's `Accept` header indicates that it will accept any response content type.
|
||||||
bool get acceptsAll => _acceptsAllCache ??= accepts('*/*');
|
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.
|
/// Manually parses the request body, if it has not already been parsed.
|
||||||
Future<BodyParseResult> parse() {
|
Future<void> parseBody({Encoding encoding: utf8}) async {
|
||||||
if (_body != null)
|
if (!_hasParsedBody) {
|
||||||
return new Future.value(_body);
|
_hasParsedBody = true;
|
||||||
else
|
|
||||||
_provisionalQuery = null;
|
|
||||||
return parseOnce().then((body) => _body = body);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Override this method to one-time parse an incoming request.
|
if (contentType.type == 'application' && contentType.subtype == 'json') {
|
||||||
@virtual
|
_bodyFiles = [];
|
||||||
Future<BodyParseResult> parseOnce();
|
|
||||||
|
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.
|
/// Disposes of all resources.
|
||||||
Future close() {
|
Future close() {
|
||||||
_body = null;
|
|
||||||
_acceptsAllCache = null;
|
_acceptsAllCache = null;
|
||||||
_acceptHeaderCache = null;
|
_acceptHeaderCache = null;
|
||||||
_provisionalQuery?.clear();
|
|
||||||
serviceParams.clear();
|
serviceParams.clear();
|
||||||
params.clear();
|
params.clear();
|
||||||
return new Future.value();
|
return new Future.value();
|
||||||
|
|
|
@ -119,10 +119,6 @@ class Angel extends Routable {
|
||||||
/// or use `lazyBody()`.
|
/// or use `lazyBody()`.
|
||||||
bool eagerParseRequestBodies = false;
|
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.
|
/// A function that renders views.
|
||||||
///
|
///
|
||||||
/// Called by [ResponseContext]@`render`.
|
/// Called by [ResponseContext]@`render`.
|
||||||
|
@ -364,7 +360,6 @@ class Angel extends Routable {
|
||||||
this.logger,
|
this.logger,
|
||||||
this.eagerParseRequestBodies: false,
|
this.eagerParseRequestBodies: false,
|
||||||
this.allowMethodOverrides: true,
|
this.allowMethodOverrides: true,
|
||||||
this.keepRawRequestBuffers: false,
|
|
||||||
this.serializer,
|
this.serializer,
|
||||||
this.viewGenerator})
|
this.viewGenerator})
|
||||||
: super(reflector) {
|
: super(reflector) {
|
||||||
|
|
|
@ -1,29 +1,10 @@
|
||||||
/// A basic server that prints "Hello, world!"
|
/// A basic server that prints "Hello, world!"
|
||||||
library performance.hello;
|
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/angel_framework.dart';
|
||||||
import 'package:angel_framework/http.dart';
|
import 'package:angel_framework/http.dart';
|
||||||
|
|
||||||
main() async {
|
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 app = new Angel();
|
||||||
var http = new AngelHttp.custom(app, startShared, useZone: false);
|
var http = new AngelHttp.custom(app, startShared, useZone: false);
|
||||||
|
|
||||||
|
@ -37,8 +18,6 @@ void start(int id) {
|
||||||
return oldHandler(e, req, res);
|
return oldHandler(e, req, res);
|
||||||
};
|
};
|
||||||
|
|
||||||
http.startServer('127.0.0.1', 3000).then((server) {
|
await http.startServer('127.0.0.1', 3000);
|
||||||
print(
|
print('Listening at ${http.uri}');
|
||||||
'Instance #$id listening at http://${server.address.address}:${server.port}');
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,18 +2,10 @@
|
||||||
library performance.hello;
|
library performance.hello;
|
||||||
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:isolate';
|
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
for (int i = 0; i < Platform.numberOfProcessors - 1; i++)
|
return HttpServer.bind('127.0.0.1', 3000, shared: true).then((server) {
|
||||||
Isolate.spawn(start, i + 1);
|
print('Listening at http://${server.address.address}:${server.port}');
|
||||||
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}');
|
|
||||||
|
|
||||||
server.listen((request) {
|
server.listen((request) {
|
||||||
if (request.uri.path == '/') {
|
if (request.uri.path == '/') {
|
||||||
|
|
|
@ -10,11 +10,11 @@ dependencies:
|
||||||
angel_http_exception: ^1.0.0
|
angel_http_exception: ^1.0.0
|
||||||
angel_model: ^1.0.0
|
angel_model: ^1.0.0
|
||||||
angel_route: ^3.0.0
|
angel_route: ^3.0.0
|
||||||
body_parser: ^1.0.0
|
|
||||||
charcode: ^1.0.0
|
charcode: ^1.0.0
|
||||||
combinator: ^1.0.0
|
combinator: ^1.0.0
|
||||||
file: ^5.0.0
|
file: ^5.0.0
|
||||||
http_parser: ^3.0.0
|
http_parser: ^3.0.0
|
||||||
|
http_server: ^0.9.0
|
||||||
http2: ">=0.1.7 <2.0.0"
|
http2: ">=0.1.7 <2.0.0"
|
||||||
logging: ">=0.11.3 <1.0.0"
|
logging: ">=0.11.3 <1.0.0"
|
||||||
matcher: ^0.12.0
|
matcher: ^0.12.0
|
||||||
|
|
Loading…
Reference in a new issue