Basic README

This commit is contained in:
Tobe O 2018-07-10 11:06:29 -04:00
parent 2409e06e6a
commit c9492122b7
14 changed files with 191 additions and 69 deletions

View file

@ -5,7 +5,7 @@ CXX_INCLUDES=-I$(HTTP_PARSER) -I$(DART_SDK)/include
.PHONY: clean debug macos all .PHONY: clean debug macos all
all: all:
printf 'Available targets:\n'\ //printf 'Available targets:\n'\
' * `debug` - Builds a debug library on MacOS\n'\ ' * `debug` - Builds a debug library on MacOS\n'\
' * `example` - Runs example/main.dart in LLDB on MacOS\n'\ ' * `example` - Runs example/main.dart in LLDB on MacOS\n'\
' * `macos` - Builds a release-mode library on MacOS\n' ' * `macos` - Builds a release-mode library on MacOS\n'

51
README.md Normal file
View file

@ -0,0 +1,51 @@
# wings
Native HTTP driver for Angel, for a nice speed boost.
Not ready for production.
## How does it work?
Typically, Angel uses the `AngelHttp` driver, which is wrapper over the `HttpServer` functionality in
`dart:io`, which in turns uses the `ServerSocket` and `Socket` functionality. This is great - Dart's standard
library comes with an HTTP server, which saves a lot of difficult in implementation.
However, abstraction tends to come with a cost. Wings seeks to minimize abstraction entirely. Rather than
using the built-in Dart network stack, Wings' HTTP server is implemented in C++ as a Dart native extension,
and the `AngelWings` driver listens to events from the extension and converts them directly into
`RequestContext` objects, without any additional abstraction within. This reduces the amount of computation
performed on each request, and helps to minimize response latency. Sending data from the response buffer in plain
Dart surprisingly is the most expensive operation, as is revealed by the Observatory.
By combining Dart's powerful VM with a native code server based on
[the same one used in Node.js](https://github.com/nodejs/http-parser),
`AngelWings` trims several milliseconds off every request, both saving resources and reducing
load times for high-traffic applications.
## How can I use it?
The intended way to use `AngelWings` is via
[`package:build_native`](https://github.com/thosakwe/build_native);
however, the situation surrounding distributing native extensions is yet far from ideal,
so this package includes pre-built binaries out-of-the-box.
Thanks to this, you can use it like any other Dart package, by installing it via Pub.
## Brief example
Using `AngelWings` is almost identical to using `AngelHttp`; however, it does
not support SSL, and therefore should be placed behind a reverse proxy like `nginx` in production.
```dart
main() async {
var app = new Angel();
var wings = new AngelWings(app, shared: true, useZone: false);
app.injectEncoders({'gzip': gzip.encoder, 'deflate': zlib.encoder});
app.get('/hello', 'Hello, native world! This is Angel WINGS.');
var fs = const LocalFileSystem();
var vDir = new VirtualDirectory(app, fs, source: fs.directory('web'));
app.use(vDir.handleRequest);
await wings.startServer('127.0.0.1', 3000);
print('Listening at http://${wings.address.address}:${wings.port}');
}
```

View file

@ -2,7 +2,9 @@ import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'dart:isolate'; import 'dart:isolate';
import 'package:angel_framework/angel_framework.dart'; import 'package:angel_framework/angel_framework.dart';
import 'package:angel_static/angel_static.dart';
import 'package:angel_wings/angel_wings.dart'; import 'package:angel_wings/angel_wings.dart';
import 'package:file/local.dart';
main() async { main() async {
for (int i = 1; i < Platform.numberOfProcessors; i++) { for (int i = 1; i < Platform.numberOfProcessors; i++) {
@ -19,6 +21,8 @@ void isolateMain(int id) {
var app = new Angel(); var app = new Angel();
var wings = new AngelWings(app, shared: true, useZone: false); var wings = new AngelWings(app, shared: true, useZone: false);
app.injectEncoders({'gzip': gzip.encoder, 'deflate': zlib.encoder});
var old = app.errorHandler; var old = app.errorHandler;
app.errorHandler = (e, req, res) { app.errorHandler = (e, req, res) {
print(e); print(e);
@ -26,7 +30,11 @@ void isolateMain(int id) {
return old(e, req, res); return old(e, req, res);
}; };
app.get('/', 'Hello, native world! This is isolate #$id.'); app.get('/hello', 'Hello, native world! This is isolate #$id.');
var fs = const LocalFileSystem();
var vDir = new VirtualDirectory(app, fs, source: fs.directory('web'));
app.use(vDir.handleRequest);
wings.startServer('127.0.0.1', 3000).then((_) { wings.startServer('127.0.0.1', 3000).then((_) {
print( print(

View file

@ -13,7 +13,7 @@ import 'package:http_parser/http_parser.dart';
import 'package:json_god/json_god.dart' as god; import 'package:json_god/json_god.dart' as god;
import 'package:mock_request/mock_request.dart'; import 'package:mock_request/mock_request.dart';
import 'package:pool/pool.dart'; import 'package:pool/pool.dart';
//import 'package:pooled_map/pooled_map.dart'; import 'package:pooled_map/pooled_map.dart';
import 'package:stack_trace/stack_trace.dart'; import 'package:stack_trace/stack_trace.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';

View file

@ -37,8 +37,8 @@ void wings_BindSocket(Dart_NativeArguments arguments)
if (shared) if (shared)
{ {
#if __APPLE__ //#if __APPLE__
#else //#else
for (unsigned long i = 0; i < serverInfoVector.size(); i++) for (unsigned long i = 0; i < serverInfoVector.size(); i++)
{ {
WingsServerInfo *server_info = serverInfoVector.at(i); WingsServerInfo *server_info = serverInfoVector.at(i);
@ -49,7 +49,7 @@ void wings_BindSocket(Dart_NativeArguments arguments)
break; break;
} }
} }
#endif //#endif
} }
if (existingIndex > -1) if (existingIndex > -1)
@ -117,6 +117,7 @@ void wings_BindSocket(Dart_NativeArguments arguments)
return; return;
} }
/*
#if __APPLE__ #if __APPLE__
ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &i, sizeof(i)); ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &i, sizeof(i));
@ -126,6 +127,7 @@ void wings_BindSocket(Dart_NativeArguments arguments)
return; return;
} }
#endif #endif
*/
if (addressLength > 4) if (addressLength > 4)
{ {

View file

@ -11,17 +11,17 @@ Dart_Handle ToCString(Dart_Handle obj, const char** out) {
return Dart_StringToCString(string, out); return Dart_StringToCString(string, out);
} }
Dart_Handle Dart_PrintToFile(Dart_Handle obj, FILE* stream) { Dart_Handle Dart_//printToFile(Dart_Handle obj, FILE* stream) {
const char *toString; const char *toString;
Dart_Handle result = ToCString(obj, &toString); Dart_Handle result = ToCString(obj, &toString);
if (Dart_IsError(result)) if (Dart_IsError(result))
return result; return result;
fprintf(stream, "%s\n", toString); f//printf(stream, "%s\n", toString);
return Dart_Null(); return Dart_Null();
} }
Dart_Handle Dart_Print(Dart_Handle obj) { Dart_Handle Dart_//print(Dart_Handle obj) {
return Dart_PrintToFile(obj, stdout); return Dart_//printToFile(obj, stdout);
} }

View file

@ -1,6 +1,6 @@
#include <dart_native_api.h> #include <dart_native_api.h>
#include <thread> #include <thread>
//#include <iostream> #include <iostream>
#include <cstdio> #include <cstdio>
#include "wings.h" #include "wings.h"
#include "wings_thread.h" #include "wings_thread.h"

View file

@ -1,5 +1,5 @@
#include <cstdlib> #include <cstdlib>
//#include <iostream> #include <iostream>
#include <string.h> #include <string.h>
#include <dart_api.h> #include <dart_api.h>
#include "wings.h" #include "wings.h"

View file

@ -76,9 +76,11 @@ class AngelWings {
final RawReceivePort _recv = new RawReceivePort(); final RawReceivePort _recv = new RawReceivePort();
final Map<String, MockHttpSession> _sessions = {}; final Map<String, MockHttpSession> _sessions = {};
final Map<int, WingsRequestContext> _staging = <int, WingsRequestContext>{}; //final Map<int, WingsRequestContext> _staging = <int, WingsRequestContext>{};
//final PooledMap<int, WingsRequestContext> _staging = final PooledMap<int, WingsRequestContext> _staging =
// new PooledMap<int, WingsRequestContext>(); new PooledMap<int, WingsRequestContext>();
final PooledMap<int, WingsRequestContext> _live =
new PooledMap<int, WingsRequestContext>();
final Uuid _uuid = new Uuid(); final Uuid _uuid = new Uuid();
InternetAddress _address; InternetAddress _address;
int _port; int _port;
@ -91,30 +93,42 @@ class AngelWings {
//final Pool _pool = new Pool(1); //final Pool _pool = new Pool(1);
static void _send(int sockfd, Uint8List data) native "Send"; static void __send(int sockfd, Uint8List data) native "Send";
static void _closeSocket(int sockfd) native "CloseSocket"; static void __closeSocket(int sockfd) native "CloseSocket";
/* void _send(WingsRequestContext req, Uint8List data) {
void _send(int sockfd, Uint8List data) { _live.update(req._sockfd, (_) {
//print('Sending ${[req._sockfd, data]}');
__send(req._sockfd, data);
return req;
});
// _pool.withResource(() { // _pool.withResource(() {
print('Sending ${[sockfd, data]}'); ////print('Sending ${[sockfd, data]}');
_sendPort.send([sockfd, data]); //_sendPort.send([sockfd, data]);
//}); //});
//_pool.withResource(() => __send(sockfd, data)); //_pool.withResource(() => __send(sockfd, data));
} }
void _closeSocket(WingsRequestContext req) { void _closeSocket(WingsRequestContext req) {
_live.remove(req._sockfd).then((_) {
if (!req._closed) {
req._closed = true;
//print('Closing ${[req._sockfd]}');
__closeSocket(req._sockfd);
}
return req;
});
//_pool.withResource(() { //_pool.withResource(() {
if (!req._closed) { //if (!req._closed) {
req._closed = true; // req._closed = true;
var sockfd = req._sockfd; // var sockfd = req._sockfd;
print('Sending ${[sockfd]}'); // //print('Sending ${[sockfd]}');
_sendPort.send([sockfd]); // _sendPort.send([sockfd]);
} //}
//}); //});
//_pool.withResource(() => __closeSocket(sockfd)); //_pool.withResource(() => __closeSocket(sockfd));
}*/ }
AngelWings(this.app, {this.shared: false, this.useZone: true}) { AngelWings(this.app, {this.shared: false, this.useZone: true}) {
_recv.handler = _handleMessage; _recv.handler = _handleMessage;
@ -172,16 +186,25 @@ class AngelWings {
} else if (x is List && x.length >= 2) { } else if (x is List && x.length >= 2) {
int sockfd = x[0], command = x[1]; int sockfd = x[0], command = x[1];
//WingsRequestContext _newRequest() => WingsRequestContext _newRequest() =>
// new WingsRequestContext._(this, sockfd, app); new WingsRequestContext._(this, sockfd, app);
//print(x); //print(x);
switch (command) { switch (command) {
case messageBegin: case messageBegin:
//print('BEGIN $sockfd'); //print('BEGIN $sockfd');
_staging[sockfd] = new WingsRequestContext._(this, sockfd, app); _staging.putIfAbsent(sockfd, _newRequest);
//_staging[sockfd] = new WingsRequestContext._(this, sockfd, app);
break; break;
case messageComplete: case messageComplete:
_staging.remove(sockfd).then((rq) {
if (rq != null) {
rq._method = methodToString(x[2] as int);
rq._addressBytes = x[5] as Uint8List;
_live.put(sockfd, rq).then((_) => _handleRequest(rq));
}
});
/*
//print('$sockfd in $_staging???'); //print('$sockfd in $_staging???');
var rq = _staging.remove(sockfd); var rq = _staging.remove(sockfd);
if (rq != null) { if (rq != null) {
@ -189,13 +212,21 @@ class AngelWings {
rq._addressBytes = x[5] as Uint8List; rq._addressBytes = x[5] as Uint8List;
_handleRequest(rq); _handleRequest(rq);
} }
*/
break; break;
case body: case body:
_staging.update(sockfd, (rq) {
(rq._body ??= new StreamController<Uint8List>())
.add(x[2] as Uint8List);
return rq;
}, defaultValue: _newRequest);
/*
var rq = _staging[sockfd]; var rq = _staging[sockfd];
if (rq != null) { if (rq != null) {
(rq._body ??= new StreamController<Uint8List>()) (rq._body ??= new StreamController<Uint8List>())
.add(x[2] as Uint8List); .add(x[2] as Uint8List);
} }
*/
break; break;
//case upgrade: //case upgrade:
// TODO: Handle WebSockets...? // TODO: Handle WebSockets...?
@ -206,13 +237,25 @@ class AngelWings {
// onUpgradedMessage(sockfd, x[2]); // onUpgradedMessage(sockfd, x[2]);
// break; // break;
case url: case url:
_staging[sockfd]?.__url = x[2] as String; _staging.update(sockfd, (rq) {
rq?.__url = x[2] as String;
return rq;
}, defaultValue: _newRequest);
//_staging[sockfd]?.__url = x[2] as String;
break; break;
case headerField: case headerField:
_staging[sockfd]?._headerField = x[2] as String; _staging.update(sockfd, (rq) {
rq?._headerField = x[2] as String;
return rq;
}, defaultValue: _newRequest);
//_staging[sockfd]?._headerField = x[2] as String;
break; break;
case headerValue: case headerValue:
_staging[sockfd]?._headerValue = x[2] as String; _staging.update(sockfd, (rq) {
rq?._headerValue = x[2] as String;
return rq;
}, defaultValue: _newRequest);
//_staging[sockfd]?._headerValue = x[2] as String;
break; break;
} }
} }
@ -317,7 +360,7 @@ class AngelWings {
return handleAngelHttpException(e, trace, req, res); return handleAngelHttpException(e, trace, req, res);
}).catchError((e, StackTrace st) { }).catchError((e, StackTrace st) {
var trace = new Trace.from(st ?? StackTrace.current).terse; var trace = new Trace.from(st ?? StackTrace.current).terse;
AngelWings._closeSocket(req._sockfd); _closeSocket(req);
// Ideally, we won't be in a position where an absolutely fatal error occurs, // Ideally, we won't be in a position where an absolutely fatal error occurs,
// but if so, we'll need to log it. // but if so, we'll need to log it.
if (app.logger != null) { if (app.logger != null) {
@ -354,8 +397,8 @@ class AngelWings {
b.writeln('HTTP/1.1 500 Internal Server Error'); b.writeln('HTTP/1.1 500 Internal Server Error');
b.writeln(); b.writeln();
_send(req._sockfd, _coerceUint8List(b.toString().codeUnits)); _send(req, _coerceUint8List(b.toString().codeUnits));
AngelWings._closeSocket(req._sockfd); _closeSocket(req);
} finally { } finally {
return null; return null;
} }
@ -469,9 +512,9 @@ class AngelWings {
return finalizers.then((_) { return finalizers.then((_) {
//print('A'); //print('A');
_send(req._sockfd, buf); _send(req, buf);
//print('B'); //print('B');
AngelWings._closeSocket(req._sockfd); _closeSocket(req);
//print('C'); //print('C');
if (req.injections.containsKey(PoolResource)) { if (req.injections.containsKey(PoolResource)) {

View file

@ -13,14 +13,14 @@ class WingsResponseContext extends ResponseContext {
if (_isClosed && !_useStream) if (_isClosed && !_useStream)
throw ResponseContext.closed(); throw ResponseContext.closed();
else if (_useStream) else if (_useStream)
AngelWings._send(correspondingRequest._sockfd, _coerceUint8List(data)); _wings._send(correspondingRequest, _coerceUint8List(data));
else else
buffer.add(data); buffer.add(data);
} }
@override @override
Future close() { Future close() {
AngelWings._closeSocket(correspondingRequest._sockfd); _wings._closeSocket(correspondingRequest);
_isClosed = true; _isClosed = true;
_useStream = false; _useStream = false;
return super.close(); return super.close();
@ -76,8 +76,8 @@ class WingsResponseContext extends ResponseContext {
} }
} }
return output.forEach(((data) => return output.forEach(
AngelWings._send(correspondingRequest._sockfd, _coerceUint8List(data)))); ((data) => _wings._send(correspondingRequest, _coerceUint8List(data))));
} }
@override @override
@ -147,8 +147,8 @@ class WingsResponseContext extends ResponseContext {
b.writeln(); b.writeln();
AngelWings._send( _wings._send(
correspondingRequest._sockfd, _coerceUint8List(b.toString().codeUnits)); correspondingRequest, _coerceUint8List(b.toString().codeUnits));
} }
} }

View file

@ -1,5 +1,5 @@
//#include <memory> #include <memory>
//#include <iostream> #include <iostream>
#include <utility> #include <utility>
#include <dart_native_api.h> #include <dart_native_api.h>
#include "wings_thread.h" #include "wings_thread.h"
@ -12,31 +12,32 @@ void wingsThreadMain(wings_thread_info *info)
while (true) while (true)
{ {
std::unique_lock<std::mutex> lock(serverInfo->mutex, std::defer_lock); //std::unique_lock<std::mutex> lock(serverInfo->mutex, std::defer_lock);
std::lock_guard<std::mutex> lock(serverInfo->mutex);
sockaddr client_addr{}; sockaddr client_addr{};
socklen_t client_addr_len; socklen_t client_addr_len;
if (lock.try_lock()) //if (lock.try_lock())
//{
int client = accept(serverInfo->sockfd, &client_addr, &client_addr_len);
//lock.unlock();
if (client < 0)
{ {
int client = accept(serverInfo->sockfd, &client_addr, &client_addr_len); // send_error(info->port, "Failed to accept client socket.");
lock.unlock(); return;
if (client < 0)
{
// send_error(info->port, "Failed to accept client socket.");
return;
}
//auto rq = std::make_shared<requestInfo>();
auto *rq = new requestInfo;
rq->ipv6 = serverInfo->ipv6;
rq->sock = client;
rq->addr = client_addr;
rq->addr_len = client_addr_len;
rq->port = port;
handleRequest(rq);
} }
//auto rq = std::make_shared<requestInfo>();
auto *rq = new requestInfo;
rq->ipv6 = serverInfo->ipv6;
rq->sock = client;
rq->addr = client_addr;
rq->addr_len = client_addr_len;
rq->port = port;
handleRequest(rq);
//}
} }
} }
@ -176,7 +177,7 @@ void handleRequest(requestInfo *rq)
}; };
settings.on_url = [](http_parser *parser, const char *at, size_t length) { settings.on_url = [](http_parser *parser, const char *at, size_t length) {
// std::cout << "url" << std::endl; // //std::cout << "url" << std::endl;
return send_string(parser, (char *)at, length, 2); return send_string(parser, (char *)at, length, 2);
}; };
@ -198,7 +199,7 @@ void handleRequest(requestInfo *rq)
unsigned int isUpgrade = 0; unsigned int isUpgrade = 0;
//std::cout << "start" << std::endl; //std::cout << "start" << std::endl;
while ((recved = recv(rq->sock, buf, len, 0)) > 0) while ((recved = recv(rq->sock, buf, len, 0)) >= 0)
{ {
if (isUpgrade) if (isUpgrade)
{ {

View file

@ -3,9 +3,10 @@ dependencies:
angel_framework: ^1.0.0 angel_framework: ^1.0.0
build_native: ^0.0.9 build_native: ^0.0.9
mock_request: ^1.0.0 mock_request: ^1.0.0
#pooled_map: ^1.0.0 pooled_map: ^1.0.0
uuid: ^1.0.0 uuid: ^1.0.0
dev_dependencies: dev_dependencies:
angel_static: ^1.3.0
build_runner: build_runner:
git: git:
url: https://github.com/thosakwe/build.git url: https://github.com/thosakwe/build.git

13
web/index.html Normal file
View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Angel Wings</title>
<link rel="stylesheet" href="site.css">
</head>
<body>
<h1>Hello!!!</h1>
</body>
</html>

3
web/site.css Normal file
View file

@ -0,0 +1,3 @@
h1 {
color: blue;
}