diff --git a/packages/body_parser/.gitignore b/packages/body_parser/.gitignore
new file mode 100644
index 00000000..b4d6e266
--- /dev/null
+++ b/packages/body_parser/.gitignore
@@ -0,0 +1,64 @@
+# Created by .ignore support plugin (hsz.mobi)
+### JetBrains template
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff:
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/dictionaries
+
+# Sensitive or high-churn files:
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.xml
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+
+# Gradle:
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# CMake
+cmake-build-debug/
+
+# Mongo Explorer plugin:
+.idea/**/mongoSettings.xml
+
+## File-based project format:
+*.iws
+
+## Plugin-specific files:
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+### Dart template
+# See https://www.dartlang.org/tools/private-files.html
+
+# Files and directories created by pub
+.packages
+.pub/
+build/
+# If you're building an application, you may want to check-in your pubspec.lock
+pubspec.lock
+
+# Directory created by dartdoc
+# If you don't generate documentation locally you can remove this line.
+doc/api/
diff --git a/packages/body_parser/.idea/body_parser.iml b/packages/body_parser/.idea/body_parser.iml
new file mode 100644
index 00000000..ae9af975
--- /dev/null
+++ b/packages/body_parser/.idea/body_parser.iml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/body_parser/.idea/libraries/Dart_Packages.xml b/packages/body_parser/.idea/libraries/Dart_Packages.xml
new file mode 100644
index 00000000..3a4aead0
--- /dev/null
+++ b/packages/body_parser/.idea/libraries/Dart_Packages.xml
@@ -0,0 +1,421 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/body_parser/.idea/modules.xml b/packages/body_parser/.idea/modules.xml
new file mode 100644
index 00000000..9c283837
--- /dev/null
+++ b/packages/body_parser/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/body_parser/.idea/runConfigurations/main_dart.xml b/packages/body_parser/.idea/runConfigurations/main_dart.xml
new file mode 100644
index 00000000..750f7262
--- /dev/null
+++ b/packages/body_parser/.idea/runConfigurations/main_dart.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/body_parser/.idea/vcs.xml b/packages/body_parser/.idea/vcs.xml
new file mode 100644
index 00000000..35eb1ddf
--- /dev/null
+++ b/packages/body_parser/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/body_parser/.travis.yml b/packages/body_parser/.travis.yml
new file mode 100644
index 00000000..a9e2c109
--- /dev/null
+++ b/packages/body_parser/.travis.yml
@@ -0,0 +1,4 @@
+language: dart
+dart:
+ - dev
+ - stable
\ No newline at end of file
diff --git a/packages/body_parser/CHANGELOG.md b/packages/body_parser/CHANGELOG.md
new file mode 100644
index 00000000..2304947d
--- /dev/null
+++ b/packages/body_parser/CHANGELOG.md
@@ -0,0 +1,5 @@
+# 1.1.1
+* Dart 2 updates; should fix Angel in Travis.
+
+# 1.1.0
+* Add `parseBodyFromStream`
\ No newline at end of file
diff --git a/packages/body_parser/LICENSE b/packages/body_parser/LICENSE
new file mode 100644
index 00000000..3832f450
--- /dev/null
+++ b/packages/body_parser/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 Tobe O
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/packages/body_parser/README.md b/packages/body_parser/README.md
new file mode 100644
index 00000000..6c577eab
--- /dev/null
+++ b/packages/body_parser/README.md
@@ -0,0 +1,82 @@
+# body_parser
+[![Pub](https://img.shields.io/pub/v/body_parser.svg)](https://pub.dartlang.org/packages/body_parser)
+[![build status](https://travis-ci.org/angel-dart/body_parser.svg)](https://travis-ci.org/angel-dart/body_parser)
+
+Parse request bodies and query strings in Dart, as well multipart/form-data uploads. No external
+dependencies required.
+
+This is the request body parser powering the
+[Angel](https://angel-dart.github.io)
+framework. If you are looking for a server-side solution with dependency injection,
+WebSockets, and more, then I highly recommend it as your first choice. Bam!
+
+### Contents
+
+* [Body Parser](#body-parser)
+* [About](#about)
+* [Installation](#installation)
+* [Usage](#usage)
+* [Thanks](#thank-you-for-using-body-parser)
+
+# About
+
+I needed something like Express.js's `body-parser` module, so I made it here. It fully supports JSON requests.
+x-www-form-urlencoded fully supported, as well as query strings. You can also include arrays in your query,
+in the same way you would for a PHP application. Full file upload support will also be present by the production 1.0.0 release.
+
+A benefit of this is that primitive types are automatically deserialized correctly. As in, if you have a `hello=1.5` request, then
+`body['hello']` will equal `1.5` and not `'1.5'`. A very semantic difference, yes, but it relieves stress in my head.
+
+# Installation
+
+To install Body Parser for your Dart project, simply add body_parser to your
+pub dependencies.
+
+ dependencies:
+ body_parser: any
+
+# Usage
+
+Body Parser exposes a simple class called `BodyParseResult`.
+You can easily parse the query string and request body for a request by calling `Future parseBody`.
+
+```dart
+import 'dart:convert';
+import 'package:body_parser/body_parser.dart';
+
+main() async {
+ // ...
+ await for (HttpRequest request in server) {
+ request.response.write(JSON.encode(await parseBody(request).body));
+ await request.response.close();
+ }
+}
+```
+
+You can also use `buildMapFromUri(Map, String)` to populate a map from a URL encoded string.
+
+This can easily be used with a library like [JSON God](https://github.com/thosakwe/json_god)
+to build structured JSON/REST APIs. Add validation and you've got an instant backend.
+
+```dart
+MyClass create(HttpRequest request) async {
+ return god.deserialize(await parseBody(request).body, MyClass);
+}
+```
+
+## Custom Body Parsing
+In cases where you need to parse unrecognized content types, `body_parser` won't be of any help to you
+on its own. However, you can use the `originalBuffer` property of a `BodyParseResult` to see the original
+request buffer. To get this functionality, pass `storeOriginalBuffer` as `true` when calling `parseBody`.
+
+For example, if you wanted to
+[parse GraphQL queries within your server](https://github.com/angel-dart/graphql)...
+
+```dart
+app.get('/graphql', (req, res) async {
+ if (req.headers.contentType.mimeType == 'application/graphql') {
+ var graphQlString = new String.fromCharCodes(req.originalBuffer);
+ // ...
+ }
+});
+```
\ No newline at end of file
diff --git a/packages/body_parser/analysis_options.yaml b/packages/body_parser/analysis_options.yaml
new file mode 100644
index 00000000..eae1e42a
--- /dev/null
+++ b/packages/body_parser/analysis_options.yaml
@@ -0,0 +1,3 @@
+analyzer:
+ strong-mode:
+ implicit-casts: false
\ No newline at end of file
diff --git a/packages/body_parser/example/main.dart b/packages/body_parser/example/main.dart
new file mode 100644
index 00000000..ce0af703
--- /dev/null
+++ b/packages/body_parser/example/main.dart
@@ -0,0 +1,41 @@
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+import 'dart:isolate';
+import 'package:body_parser/body_parser.dart';
+
+main() async {
+ var address = '127.0.0.1';
+ var port = 3000;
+ var futures = [];
+
+ for (int i = 1; i < Platform.numberOfProcessors; i++) {
+ futures.add(Isolate.spawn(start, [address, port, i]));
+ }
+
+ Future.wait(futures).then((_) {
+ print('All instances started.');
+ print(
+ 'Test with "wrk -t12 -c400 -d30s -s ./example/post.lua http://localhost:3000" or similar');
+ start([address, port, 0]);
+ });
+}
+
+void start(List args) {
+ var address = new InternetAddress(args[0] as String);
+ int port = args[1], id = args[2];
+
+ HttpServer.bind(address, port, shared: true).then((server) {
+ server.listen((request) async {
+ // ignore: deprecated_member_use
+ var body = await parseBody(request);
+ request.response
+ ..headers.contentType = new ContentType('application', 'json')
+ ..write(json.encode(body.body))
+ ..close();
+ });
+
+ print(
+ 'Server #$id listening at http://${server.address.address}:${server.port}');
+ });
+}
diff --git a/packages/body_parser/example/post.lua b/packages/body_parser/example/post.lua
new file mode 100644
index 00000000..524febc6
--- /dev/null
+++ b/packages/body_parser/example/post.lua
@@ -0,0 +1,6 @@
+-- example HTTP POST script which demonstrates setting the
+-- HTTP method, body, and adding a header
+
+wrk.method = "POST"
+wrk.body = "foo=bar&baz=quux"
+wrk.headers["Content-Type"] = "application/x-www-form-urlencoded"
\ No newline at end of file
diff --git a/packages/body_parser/lib/body_parser.dart b/packages/body_parser/lib/body_parser.dart
new file mode 100644
index 00000000..0541b3e6
--- /dev/null
+++ b/packages/body_parser/lib/body_parser.dart
@@ -0,0 +1,6 @@
+/// A library for parsing HTTP request bodies and queries.
+library body_parser;
+
+export 'src/body_parse_result.dart';
+export 'src/file_upload_info.dart';
+export 'src/parse_body.dart';
diff --git a/packages/body_parser/lib/src/body_parse_result.dart b/packages/body_parser/lib/src/body_parse_result.dart
new file mode 100644
index 00000000..806335c3
--- /dev/null
+++ b/packages/body_parser/lib/src/body_parse_result.dart
@@ -0,0 +1,28 @@
+import 'file_upload_info.dart';
+
+/// A representation of data from an incoming request.
+abstract class BodyParseResult {
+ /// The parsed body.
+ Map get body;
+
+ /// The parsed query string.
+ Map get query;
+
+ /// All files uploaded within this request.
+ List get files;
+
+ /// The original body bytes sent with this request.
+ ///
+ /// You must set [storeOriginalBuffer] to `true` to see this.
+ List get originalBuffer;
+
+ /// If an error was encountered while parsing the body, it will appear here.
+ ///
+ /// Otherwise, this is `null`.
+ dynamic get error;
+
+ /// If an error was encountered while parsing the body, the call stack will appear here.
+ ///
+ /// Otherwise, this is `null`.
+ StackTrace get stack;
+}
diff --git a/packages/body_parser/lib/src/chunk.dart b/packages/body_parser/lib/src/chunk.dart
new file mode 100644
index 00000000..58776ca0
--- /dev/null
+++ b/packages/body_parser/lib/src/chunk.dart
@@ -0,0 +1,7 @@
+import 'file_upload_info.dart';
+
+List getFileDataFromChunk(
+ String chunk, String boundary, String fileUploadName, Map body) {
+ List result = [];
+ return result;
+}
diff --git a/packages/body_parser/lib/src/file_upload_info.dart b/packages/body_parser/lib/src/file_upload_info.dart
new file mode 100644
index 00000000..5db8b785
--- /dev/null
+++ b/packages/body_parser/lib/src/file_upload_info.dart
@@ -0,0 +1,17 @@
+/// Represents a file uploaded to the server.
+class FileUploadInfo {
+ /// The MIME type of the uploaded file.
+ String mimeType;
+
+ /// The name of the file field from the request.
+ String name;
+
+ /// The filename of the file.
+ String filename;
+
+ /// The bytes that make up this file.
+ List data;
+
+ FileUploadInfo(
+ {this.mimeType, this.name, this.filename, this.data: const []}) {}
+}
diff --git a/packages/body_parser/lib/src/get_value.dart b/packages/body_parser/lib/src/get_value.dart
new file mode 100644
index 00000000..d3c6c3f1
--- /dev/null
+++ b/packages/body_parser/lib/src/get_value.dart
@@ -0,0 +1,20 @@
+import 'package:dart2_constant/convert.dart';
+
+getValue(String value) {
+ try {
+ num numValue = num.parse(value);
+ if (!numValue.isNaN)
+ return numValue;
+ else
+ return value;
+ } on FormatException {
+ if (value.startsWith('[') && value.endsWith(']'))
+ return json.decode(value);
+ else if (value.startsWith('{') && value.endsWith('}'))
+ return json.decode(value);
+ else if (value.trim().toLowerCase() == 'null')
+ return null;
+ else
+ return value;
+ }
+}
diff --git a/packages/body_parser/lib/src/map_from_uri.dart b/packages/body_parser/lib/src/map_from_uri.dart
new file mode 100644
index 00000000..0945ce6b
--- /dev/null
+++ b/packages/body_parser/lib/src/map_from_uri.dart
@@ -0,0 +1,43 @@
+import 'get_value.dart';
+
+/// Parses a URI-encoded string into real data! **Wow!**
+///
+/// Whichever map you provide will be automatically populated from the urlencoded body string you provide.
+buildMapFromUri(Map map, String body) {
+ RegExp parseArrayRgx = new RegExp(r'^(.+)\[\]$');
+
+ for (String keyValuePair in body.split('&')) {
+ if (keyValuePair.contains('=')) {
+ var equals = keyValuePair.indexOf('=');
+ String key = Uri.decodeQueryComponent(keyValuePair.substring(0, equals));
+ String value =
+ Uri.decodeQueryComponent(keyValuePair.substring(equals + 1));
+
+ if (parseArrayRgx.hasMatch(key)) {
+ Match queryMatch = parseArrayRgx.firstMatch(key);
+ key = queryMatch.group(1);
+ if (!(map[key] is List)) {
+ map[key] = [];
+ }
+
+ map[key].add(getValue(value));
+ } else if (key.contains('.')) {
+ // i.e. map.foo.bar => [map, foo, bar]
+ List keys = key.split('.');
+
+ Map targetMap = map[keys[0]] != null ? map[keys[0]] as Map : {};
+ map[keys[0]] = targetMap;
+ for (int i = 1; i < keys.length; i++) {
+ if (i < keys.length - 1) {
+ targetMap[keys[i]] = targetMap[keys[i]] ?? {};
+ targetMap = targetMap[keys[i]] as Map;
+ } else {
+ targetMap[keys[i]] = getValue(value);
+ }
+ }
+ } else
+ map[key] = getValue(value);
+ } else
+ map[Uri.decodeQueryComponent(keyValuePair)] = true;
+ }
+}
diff --git a/packages/body_parser/lib/src/parse_body.dart b/packages/body_parser/lib/src/parse_body.dart
new file mode 100644
index 00000000..8fdb94b9
--- /dev/null
+++ b/packages/body_parser/lib/src/parse_body.dart
@@ -0,0 +1,151 @@
+import 'dart:async';
+import 'dart:io';
+import 'dart:typed_data';
+
+import 'package:dart2_constant/convert.dart';
+import 'package:http_parser/http_parser.dart';
+import 'package:http_server/http_server.dart';
+import 'package:mime/mime.dart';
+
+import 'body_parse_result.dart';
+import 'file_upload_info.dart';
+import 'map_from_uri.dart';
+
+/// Forwards to [parseBodyFromStream].
+@deprecated
+Future parseBody(HttpRequest request,
+ {bool storeOriginalBuffer: false}) {
+ return parseBodyFromStream(
+ request,
+ request.headers.contentType != null
+ ? new MediaType.parse(request.headers.contentType.toString())
+ : null,
+ request.uri,
+ storeOriginalBuffer: storeOriginalBuffer);
+}
+
+/// Grabs data from an incoming request.
+///
+/// Supports URL-encoded and JSON, as well as multipart/* forms.
+/// On a file upload request, only fields with the name **'file'** are processed
+/// as files. Anything else is put in the body. You can change the upload file name
+/// via the *fileUploadName* parameter. :)
+///
+/// Use [storeOriginalBuffer] to add the original request bytes to the result.
+Future parseBodyFromStream(
+ Stream data, MediaType contentType, Uri requestUri,
+ {bool storeOriginalBuffer: false}) async {
+ var result = new _BodyParseResultImpl();
+
+ Future getBytes() {
+ return data
+ .fold(new BytesBuilder(copy: false), (a, b) => a..add(b))
+ .then((b) => b.takeBytes());
+ }
+
+ Future getBody() {
+ if (storeOriginalBuffer) {
+ return getBytes().then((bytes) {
+ result.originalBuffer = bytes;
+ return utf8.decode(bytes);
+ });
+ } else {
+ return utf8.decoder.bind(data).join();
+ }
+ }
+
+ try {
+ if (contentType != null) {
+ if (contentType.type == 'multipart' &&
+ contentType.parameters.containsKey('boundary')) {
+ Stream stream;
+
+ if (storeOriginalBuffer) {
+ var bytes = result.originalBuffer = await getBytes();
+ var ctrl = new StreamController()
+ ..add(bytes)
+ ..close();
+ stream = ctrl.stream;
+ } else {
+ stream = data;
+ }
+
+ var parts = MimeMultipartTransformer(
+ contentType.parameters['boundary']).bind(stream)
+ .map((part) =>
+ HttpMultipartFormData.parse(part, defaultEncoding: utf8));
+
+ await for (HttpMultipartFormData part in parts) {
+ if (part.isBinary ||
+ part.contentDisposition.parameters.containsKey("filename")) {
+ BytesBuilder builder = await part.fold(
+ new BytesBuilder(copy: false),
+ (BytesBuilder b, d) => b
+ ..add(d is! String
+ ? (d as List)
+ : (d as String).codeUnits));
+ var upload = new FileUploadInfo(
+ mimeType: part.contentType.mimeType,
+ name: part.contentDisposition.parameters['name'],
+ filename:
+ part.contentDisposition.parameters['filename'] ?? 'file',
+ data: builder.takeBytes());
+ result.files.add(upload);
+ } else if (part.isText) {
+ var text = await part.join();
+ buildMapFromUri(result.body,
+ '${part.contentDisposition.parameters["name"]}=$text');
+ }
+ }
+ } else if (contentType.mimeType == 'application/json') {
+ result.body
+ .addAll(_foldToStringDynamic(json.decode(await getBody()) as Map));
+ } else if (contentType.mimeType == 'application/x-www-form-urlencoded') {
+ String body = await getBody();
+ buildMapFromUri(result.body, body);
+ } else if (storeOriginalBuffer == true) {
+ result.originalBuffer = await getBytes();
+ }
+ } else {
+ if (requestUri.hasQuery) {
+ buildMapFromUri(result.query, requestUri.query);
+ }
+
+ if (storeOriginalBuffer == true) {
+ result.originalBuffer = await getBytes();
+ }
+ }
+ } catch (e, st) {
+ result.error = e;
+ result.stack = st;
+ }
+
+ return result;
+}
+
+class _BodyParseResultImpl implements BodyParseResult {
+ @override
+ Map body = {};
+
+ @override
+ List files = [];
+
+ @override
+ List originalBuffer;
+
+ @override
+ Map query = {};
+
+ @override
+ var error = null;
+
+ @override
+ StackTrace stack = null;
+}
+
+Map _foldToStringDynamic(Map map) {
+ return map == null
+ ? null
+ : map.keys.fold