From 6a16a057fcca5c39af0e444b43ecab2548d8d91d Mon Sep 17 00:00:00 2001 From: Tobe O Date: Tue, 3 Jul 2018 19:16:29 -0400 Subject: [PATCH 01/28] :fire: --- .gitignore | 21 ++ analysis_options.yaml | 3 + example/main.dart | 28 ++ lib/angel_wings.dart | 21 ++ lib/src/bind_socket.cc | 199 +++++++++++++ lib/src/http_listener.cc | 50 ++++ lib/src/libwings.build_native.yaml | 18 ++ lib/src/send.cc | 22 ++ lib/src/util.cc | 36 +++ lib/src/wings.cc | 63 ++++ lib/src/wings.dart | 451 +++++++++++++++++++++++++++++ lib/src/wings.h | 45 +++ lib/src/wings_request.dart | 157 ++++++++++ lib/src/wings_response.dart | 158 ++++++++++ lib/src/wings_thread.h | 25 ++ lib/src/worker_thread.cc | 215 ++++++++++++++ pubspec.yaml | 13 + 17 files changed, 1525 insertions(+) create mode 100644 .gitignore create mode 100644 analysis_options.yaml create mode 100644 example/main.dart create mode 100644 lib/angel_wings.dart create mode 100644 lib/src/bind_socket.cc create mode 100644 lib/src/http_listener.cc create mode 100644 lib/src/libwings.build_native.yaml create mode 100644 lib/src/send.cc create mode 100644 lib/src/util.cc create mode 100644 lib/src/wings.cc create mode 100644 lib/src/wings.dart create mode 100644 lib/src/wings.h create mode 100644 lib/src/wings_request.dart create mode 100644 lib/src/wings_response.dart create mode 100644 lib/src/wings_thread.h create mode 100644 lib/src/worker_thread.cc create mode 100644 pubspec.yaml diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..2e7f80df --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +# See https://www.dartlang.org/guides/libraries/private-files + +# Files and directories created by pub +.dart_tool/ +.packages +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/ + +# Avoid committing generated Javascript files: +*.dart.js +*.info.json # Produced by the --dump-info flag. +*.js # When generated by dart2js. Don't specify *.js if your + # project includes source files written in JavaScript. +*.js_ +*.js.deps +*.js.map \ No newline at end of file diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 00000000..eae1e42a --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,3 @@ +analyzer: + strong-mode: + implicit-casts: false \ No newline at end of file diff --git a/example/main.dart b/example/main.dart new file mode 100644 index 00000000..8d0f950b --- /dev/null +++ b/example/main.dart @@ -0,0 +1,28 @@ +import 'dart:async'; +import 'dart:io'; +import 'dart:isolate'; +import 'package:angel_framework/angel_framework.dart'; +import 'package:angel_wings/angel_wings.dart'; + +main() async { + for (int i = 1; i < Platform.numberOfProcessors; i++) { + var onError = new ReceivePort(); + Isolate.spawn(isolateMain, i, onError: onError.sendPort); + onError.listen((e) => Zone.current + .handleUncaughtError(e[0], new StackTrace.fromString(e[1].toString()))); + } + + isolateMain(0); +} + +void isolateMain(int id) { + var app = new Angel(); + var wings = new AngelWings(app, shared: true); + + app.get('/', 'Hello, native world!'); + + wings.startServer('127.0.0.1', 3000).then((_) { + print( + 'Instance #$id listening at http://${wings.address.address}:${wings.port}'); + }); +} diff --git a/lib/angel_wings.dart b/lib/angel_wings.dart new file mode 100644 index 00000000..5fb2763a --- /dev/null +++ b/lib/angel_wings.dart @@ -0,0 +1,21 @@ +library angel_wings; + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'dart:isolate'; +import 'dart:typed_data'; +import 'dart-ext:src/wings'; +import 'package:angel_framework/angel_framework.dart'; +import 'package:body_parser/body_parser.dart'; +import 'package:combinator/combinator.dart'; +import 'package:http_parser/http_parser.dart'; +import 'package:mock_request/mock_request.dart'; +import 'package:pool/pool.dart'; +import 'package:pooled_map/pooled_map.dart'; +import 'package:stack_trace/stack_trace.dart'; +import 'package:tuple/tuple.dart'; +import 'package:uuid/uuid.dart'; +part 'src/wings_request.dart'; +part 'src/wings_response.dart'; +part 'src/wings.dart'; diff --git a/lib/src/bind_socket.cc b/lib/src/bind_socket.cc new file mode 100644 index 00000000..995ef857 --- /dev/null +++ b/lib/src/bind_socket.cc @@ -0,0 +1,199 @@ +#include +#include +#include +#include "wings.h" + +std::vector serverInfoVector; +std::mutex serverInfoVectorMutex; + +void wings_BindSocket(Dart_NativeArguments arguments) +{ + // Uint8List address, String addressString, int port, int backlog, bool shared + Dart_Handle addressHandle = Dart_GetNativeArgument(arguments, 0); + Dart_Handle addressStringHandle = Dart_GetNativeArgument(arguments, 1); + Dart_Handle portHandle = Dart_GetNativeArgument(arguments, 2); + Dart_Handle backlogHandle = Dart_GetNativeArgument(arguments, 3); + Dart_Handle sharedHandle = Dart_GetNativeArgument(arguments, 4); + Dart_TypedData_Type addressType; + void *addressData; + intptr_t addressLength; + const char *addressString; + uint64_t port, backlog; + bool shared; + + // Read the arguments... + HandleError(Dart_TypedDataAcquireData(addressHandle, &addressType, &addressData, &addressLength)); + HandleError(Dart_TypedDataReleaseData(addressHandle)); + HandleError(Dart_StringToCString(addressStringHandle, &addressString)); + HandleError(Dart_IntegerToUint64(portHandle, &port)); + HandleError(Dart_IntegerToUint64(backlogHandle, &backlog)); + HandleError(Dart_BooleanValue(sharedHandle, &shared)); + + // See if there is already a server bound to the port. + long existingIndex = -1; + std::string addressStringInstance(addressString); + std::lock_guard lock(serverInfoVectorMutex); + + if (shared) + { + for (unsigned long i = 0; i < serverInfoVector.size(); i++) + { + WingsServerInfo *server_info = serverInfoVector.at(i); + + if (server_info->addressString == addressStringInstance && server_info->port == port) + { + existingIndex = (long) i; + break; + } + } + } + + if (existingIndex > -1) + { + // We found an existing socket, just return a reference to it. + Dart_SetReturnValue(arguments, Dart_NewIntegerFromUint64(existingIndex)); + return; + } + else + { + // There's no existing server, so bind a new one, and add it to the serverInfoVector. +#ifndef WIN32 + int sockfd; +#else + WSADATA wsaData; + SOCKET ConnectSocket = INVALID_SOCKET; + + // Initialize Winsock + iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); + if (iResult != 0) + { + Dart_Handle errorHandle = Dart_NewList(2); + Dart_ListSetAt(errorHandle, 0, Dart_NewStringFromCString("WSAStartup failed.")); + Dart_ListSetAt(errorHandle, 1, Dart_NewInteger(iResult)); + Dart_ThrowException(errorHandle); + return 1; + } + + // TODO: Rest of Windows config: + // https://docs.microsoft.com/en-us/windows/desktop/winsock/complete-client-code +#endif + + if (addressLength == 4) + { + // IPv4 + sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + } + else + { + // IPv6 + sockfd = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); + } + + if (sockfd < 0) + { + Dart_Handle errorHandle = Dart_NewList(3); + Dart_ListSetAt(errorHandle, 0, Dart_NewStringFromCString("Failed to create socket.")); + Dart_ListSetAt(errorHandle, 1, Dart_NewStringFromCString(strerror(errno))); + Dart_ListSetAt(errorHandle, 2, Dart_NewInteger(errno)); + Dart_ThrowException(errorHandle); + return; + } + + int i = 1; + int ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)); + + if (ret < 0) + { + + Dart_Handle errorHandle = Dart_NewList(3); + Dart_ListSetAt(errorHandle, 0, Dart_NewStringFromCString("Cannot reuse address for socket.")); + Dart_ListSetAt(errorHandle, 1, Dart_NewStringFromCString(strerror(errno))); + Dart_ListSetAt(errorHandle, 2, Dart_NewInteger(errno)); + Dart_ThrowException(errorHandle); + return; + } + + /* + ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &i, sizeof(i)); + + if (ret < 0) + { + Dart_ThrowException(Dart_NewStringFromCString("Cannot reuse port for socket.")); + return; + } + */ + + if (addressLength > 4) + { + struct sockaddr_in6 v6 + { + }; + memset(&v6, 0, sizeof(v6)); + v6.sin6_family = AF_INET6; + v6.sin6_port = htons((uint16_t)port); + ret = inet_pton(v6.sin6_family, addressString, &v6.sin6_addr.s6_addr); + + if (ret >= 0) + ret = bind(sockfd, (const sockaddr *)&v6, sizeof(v6)); + } + else + { + struct sockaddr_in v4 + { + }; + memset(&v4, 0, sizeof(v4)); + v4.sin_family = AF_INET; + v4.sin_port = htons((uint16_t)port); + v4.sin_addr.s_addr = inet_addr(addressString); + + if (ret >= 0) + ret = bind(sockfd, (const sockaddr *)&v4, sizeof(v4)); + //ret = inet_pton(family, host, &v4.sin_addr); + } + + /*if (ret < 1) { + Dart_ThrowException(Dart_NewStringFromCString("Cannot parse IP address.")); + return; + }*/ + + //if (bind(sock, (const sockaddr *) &serveraddr, sizeof(serveraddr)) < 0) { + if (ret < 0) + { + Dart_Handle errorHandle = Dart_NewList(3); + Dart_ListSetAt(errorHandle, 0, Dart_NewStringFromCString("Failed to bind socket.")); + Dart_ListSetAt(errorHandle, 1, Dart_NewStringFromCString(strerror(errno))); + Dart_ListSetAt(errorHandle, 2, Dart_NewInteger(errno)); + Dart_ThrowException(errorHandle); + return; + } + + if (listen(sockfd, SOMAXCONN) < 0) + { + Dart_Handle errorHandle = Dart_NewList(3); + Dart_ListSetAt(errorHandle, 0, Dart_NewStringFromCString("Failed to listen to bound socket.")); + Dart_ListSetAt(errorHandle, 1, Dart_NewStringFromCString(strerror(errno))); + Dart_ListSetAt(errorHandle, 2, Dart_NewInteger(errno)); + Dart_ThrowException(errorHandle); + return; + } + + if (listen(sockfd, (int)backlog) < 0) + { + Dart_Handle errorHandle = Dart_NewList(3); + Dart_ListSetAt(errorHandle, 0, Dart_NewStringFromCString("Failed to listen to bound socket.")); + Dart_ListSetAt(errorHandle, 1, Dart_NewStringFromCString(strerror(errno))); + Dart_ListSetAt(errorHandle, 2, Dart_NewInteger(errno)); + Dart_ThrowException(errorHandle); + return; + } + + // Now that we've bound the socket, let's add it to the list. + auto *server_info = new WingsServerInfo; + server_info->sockfd = sockfd; + server_info->port = port; + server_info->ipv6 = addressLength > 4; + server_info->addressString += addressStringInstance; + Dart_SetReturnValue(arguments, Dart_NewIntegerFromUint64(serverInfoVector.size())); + serverInfoVector.push_back(server_info); + } +} \ No newline at end of file diff --git a/lib/src/http_listener.cc b/lib/src/http_listener.cc new file mode 100644 index 00000000..534466b6 --- /dev/null +++ b/lib/src/http_listener.cc @@ -0,0 +1,50 @@ +#include +#include +#include "wings.h" +#include "wings_thread.h" + +void handleMessage(Dart_Port destPortId, Dart_CObject *message); + +void wings_StartHttpListener(Dart_NativeArguments arguments) +{ + Dart_Port port = Dart_NewNativePort("angel_wings", handleMessage, true); + Dart_SetReturnValue(arguments, Dart_NewSendPort(port)); +} + +int64_t get_int(Dart_CObject *obj) +{ + if (obj == nullptr) + return 0; + switch (obj->type) + { + case Dart_CObject_kInt32: + return (int64_t)obj->value.as_int32; + case Dart_CObject_kInt64: + return obj->value.as_int64; + default: + return 0; + } +} + +void handleMessage(Dart_Port destPortId, Dart_CObject *message) +{ + // We always expect an array to be sent. + Dart_CObject_Type firstType = message->value.as_array.values[0]->type; + + // If it's a SendPort, then start a new thread that listens for incoming connections. + if (firstType == Dart_CObject_kSendPort) + { + std::lock_guard lock(serverInfoVectorMutex); + auto *threadInfo = new wings_thread_info; + threadInfo->port = message->value.as_array.values[0]->value.as_send_port.id; + threadInfo->serverInfo = serverInfoVector.at((unsigned long)get_int(message->value.as_array.values[1])); + std::thread workerThread(wingsThreadMain, threadInfo); + workerThread.detach(); + } + else if (firstType == Dart_CObject_kBool) + { + // The Dart world is trying to close this port. + Dart_Port port = message->value.as_array.values[1]->value.as_send_port.id; + Dart_CloseNativePort(port); + } +} \ No newline at end of file diff --git a/lib/src/libwings.build_native.yaml b/lib/src/libwings.build_native.yaml new file mode 100644 index 00000000..9fd5b606 --- /dev/null +++ b/lib/src/libwings.build_native.yaml @@ -0,0 +1,18 @@ +include: + - angel_wings|lib/src/wings.h + - angel_wings|lib/src/wings_thread.h +sources: + - angel_wings|lib/src/bind_socket.cc + - angel_wings|lib/src/http_listener.cc + - angel_wings|lib/src/send.cc + - angel_wings|lib/src/util.cc + - angel_wings|lib/src/wings.cc + - angel_wings|lib/src/worker_thread.cc +third_party: + http_parser: + git: https://github.com/nodejs/http-parser.git + commit: 5b76466 + include: + - . + sources: + - http_parser.c \ No newline at end of file diff --git a/lib/src/send.cc b/lib/src/send.cc new file mode 100644 index 00000000..45c0414d --- /dev/null +++ b/lib/src/send.cc @@ -0,0 +1,22 @@ +#include "wings.h" + +void wings_CloseSocket(Dart_NativeArguments arguments) +{ + Dart_Handle sockfdHandle = Dart_GetNativeArgument(arguments, 0); + uint64_t sockfd; + HandleError(Dart_IntegerToUint64(sockfdHandle, &sockfd)); + close((int)sockfd); +} + +void wings_Send(Dart_NativeArguments arguments) +{ + Dart_Handle sockfdHandle = Dart_GetNativeArgument(arguments, 0); + Dart_Handle dataHandle = Dart_GetNativeArgument(arguments, 1); + uint64_t sockfd; + Dart_TypedData_Type dataType; + void *dataBytes; + intptr_t dataLength; + HandleError(Dart_IntegerToUint64(sockfdHandle, &sockfd)); + HandleError(Dart_TypedDataAcquireData(dataHandle, &dataType, &dataBytes, &dataLength)); + write((int)sockfd, dataBytes, (size_t)dataLength); +} \ No newline at end of file diff --git a/lib/src/util.cc b/lib/src/util.cc new file mode 100644 index 00000000..be5017d5 --- /dev/null +++ b/lib/src/util.cc @@ -0,0 +1,36 @@ +#include +#include "wings.h" + +void wings_AddressToString(Dart_NativeArguments arguments) { + char *address; + void *data; + intptr_t length; + bool ipv6; + Dart_TypedData_Type type; + + Dart_Handle address_handle = Dart_GetNativeArgument(arguments, 0); + Dart_Handle ipv6_handle = Dart_GetNativeArgument(arguments, 1); + HandleError(Dart_BooleanValue(ipv6_handle, &ipv6)); + sa_family_t family; + + if (ipv6) { + family = AF_INET6; + address = (char *) Dart_ScopeAllocate(INET6_ADDRSTRLEN); + } else { + family = AF_INET; + address = (char *) Dart_ScopeAllocate(INET_ADDRSTRLEN); + } + + HandleError(Dart_TypedDataAcquireData(address_handle, &type, &data, &length)); + auto *ptr = inet_ntop(family, data, address, INET_ADDRSTRLEN); + HandleError(Dart_TypedDataReleaseData(address_handle)); + + if (ptr == nullptr) { + if (ipv6) + Dart_ThrowException(Dart_NewStringFromCString("Invalid IPV6 address.")); + else + Dart_ThrowException(Dart_NewStringFromCString("Invalid IPV4 address.")); + } else { + Dart_SetReturnValue(arguments, Dart_NewStringFromCString(address)); + } +} \ No newline at end of file diff --git a/lib/src/wings.cc b/lib/src/wings.cc new file mode 100644 index 00000000..41ab5b15 --- /dev/null +++ b/lib/src/wings.cc @@ -0,0 +1,63 @@ +#include +#include +#include +#include +#include "wings.h" + +// Forward declaration of ResolveName function. +Dart_NativeFunction ResolveName(Dart_Handle name, int argc, bool *auto_setup_scope); + +// The name of the initialization function is the extension name followed +// by _Init. +DART_EXPORT Dart_Handle wings_Init(Dart_Handle parent_library) +{ + if (Dart_IsError(parent_library)) + return parent_library; + + Dart_Handle result_code = + Dart_SetNativeResolver(parent_library, ResolveName, NULL); + if (Dart_IsError(result_code)) + return result_code; + + return Dart_Null(); +} + +Dart_Handle HandleError(Dart_Handle handle) +{ + if (Dart_IsError(handle)) + Dart_PropagateError(handle); + return handle; +} + +Dart_NativeFunction ResolveName(Dart_Handle name, int argc, bool *auto_setup_scope) +{ + // If we fail, we return NULL, and Dart throws an exception. + if (!Dart_IsString(name)) + return NULL; + Dart_NativeFunction result = NULL; + const char *cname; + HandleError(Dart_StringToCString(name, &cname)); + + if (strcmp(cname, "AddressToString") == 0) + { + result = wings_AddressToString; + } + else if (strcmp(cname, "BindSocket") == 0) + { + result = wings_BindSocket; + } + else if (strcmp(cname, "CloseSocket") == 0) + { + result = wings_CloseSocket; + } + else if (strcmp(cname, "Send") == 0) + { + result = wings_Send; + } + else if (strcmp(cname, "StartHttpListener") == 0) + { + result = wings_StartHttpListener; + } + + return result; +} \ No newline at end of file diff --git a/lib/src/wings.dart b/lib/src/wings.dart new file mode 100644 index 00000000..67faf25a --- /dev/null +++ b/lib/src/wings.dart @@ -0,0 +1,451 @@ +part of angel_wings; + +class AngelWings { + static const int messageBegin = 0, + messageComplete = 1, + url = 2, + headerField = 3, + headerValue = 4, + body = 5, + upgrade = 6, + upgradedMessage = 7; + + static const int DELETE = 0, + GET = 1, + HEAD = 2, + POST = 3, + PUT = 4, + CONNECT = 5, + OPTIONS = 6, + TRACE = 7, + COPY = 8, + LOCK = 9, + MKCOL = 10, + MOVE = 11, + PROPFIND = 12, + PROPPATCH = 13, + SEARCH = 14, + UNLOCK = 15, + BIND = 16, + REBIND = 17, + UNBIND = 18, + ACL = 19, + REPORT = 20, + MKACTIVITY = 21, + CHECKOUT = 22, + MERGE = 23, + MSEARCH = 24, + NOTIFY = 25, + SUBSCRIBE = 26, + UNSUBSCRIBE = 27, + PATCH = 28, + PURGE = 29, + MKCALENDAR = 30, + LINK = 31, + UNLINK = 32, + SOURCE = 33; + + static String methodToString(int method) { + switch (method) { + case DELETE: + return 'DELETE'; + case GET: + return 'GET'; + case HEAD: + return 'HEAD'; + case POST: + return 'POST'; + case PUT: + return 'PUT'; + case CONNECT: + return 'CONNECT'; + case OPTIONS: + return 'OPTIONS'; + case PATCH: + return 'PATCH'; + case PURGE: + return 'PURGE'; + default: + throw new ArgumentError('Unknown method $method.'); + } + } + + final Angel app; + final bool shared; + final bool useZone; + + final RawReceivePort _recv = new RawReceivePort(); + final Map _sessions = {}; + final PooledMap _staging = + new PooledMap(); + final Uuid _uuid = new Uuid(); + InternetAddress _address; + int _port; + SendPort _sendPort; + + static int _bindSocket(Uint8List address, String addressString, int port, + int backlog, bool shared) native "BindSocket"; + + static SendPort _startHttpListener() native "StartHttpListener"; + + AngelWings(this.app, {this.shared: false, this.useZone: true}) { + _recv.handler = _handleMessage; + } + + InternetAddress get address => _address; + + int get port => _port; + + Future startServer([host, int port, int backlog = 10]) { + Future _addr = host is InternetAddress + ? host + : InternetAddress.lookup(host?.toString() ?? '127.0.0.1').then((list) => + list.isNotEmpty + ? list.first + : throw new StateError('IP lookup failed.')); + + return _addr.then((address) { + try { + var serverInfoIndex = _bindSocket( + new Uint8List.fromList(address.rawAddress), + address.address, + port ?? 0, + backlog, + shared); + _sendPort = _startHttpListener(); + _sendPort.send([_recv.sendPort, serverInfoIndex]); + _address = address; + _port = port; + } on List catch (osError) { + if (osError.length == 3) { + throw new SocketException(osError[0] as String, + osError: new OSError(osError[1] as String, osError[2] as int)); + } else { + throw new SocketException('Could not start Wings server.', + osError: new OSError(osError[0] as String, osError[1] as int)); + } + } on String catch (message) { + throw new SocketException(message); + } + }); + } + + Future close() { + _sendPort.send([true, _sendPort]); + _recv.close(); + return new Future.value(); + } + + void _handleMessage(x) { + if (x is String) { + close(); + throw new StateError(x); + } else if (x is List && x.length >= 2) { + int sockfd = x[0], command = x[1]; + + WingsRequestContext _newRequest() => + new WingsRequestContext._(this, sockfd, app); + //print(x); + + switch (command) { + case messageBegin: + _staging.putIfAbsent(sockfd, _newRequest); + break; + case messageComplete: + // (sockfd, method, major, minor, addrBytes) + _staging.update(sockfd, (rq) { + rq._method = methodToString(x[2] as int); + rq._addressBytes = x[5] as Uint8List; + return rq; + }, defaultValue: _newRequest).then(_handleRequest); + break; + case body: + _staging.update(sockfd, (rq) { + (rq._body ??= new StreamController()) + .add(x[2] as Uint8List); + return rq; + }, defaultValue: _newRequest); + break; + //case upgrade: + // TODO: Handle WebSockets...? + // if (onUpgrade != null) onUpgrade(sockfd); + // break; + //case upgradedMessage: + // TODO: Handle upgrade + // onUpgradedMessage(sockfd, x[2]); + // break; + case url: + _staging.update(sockfd, (rq) => rq..__url = x[2] as String, + defaultValue: _newRequest); + break; + case headerField: + _staging.update(sockfd, (rq) => rq.._headerField = x[2] as String, + defaultValue: _newRequest); + break; + case headerValue: + _staging.update(sockfd, (rq) => rq.._headerValue = x[2] as String, + defaultValue: _newRequest); + break; + } + } + } + + Future _handleRequest(WingsRequestContext req) { + if (req == null) return new Future.value(); + var res = new WingsResponseContext._(req) + ..app = app + ..serializer = app.serializer + ..encoders.addAll(app.encoders); + + handle() { + var path = req.path; + if (path == '/') path = ''; + + Tuple3>> resolveTuple() { + Router r = app.optimizedRouter; + var resolved = + r.resolveAbsolute(path, method: req.method, strip: false); + + return new Tuple3( + new MiddlewarePipeline(resolved).handlers, + resolved.fold({}, (out, r) => out..addAll(r.allParams)), + resolved.isEmpty ? null : resolved.first.parseResult, + ); + } + + var cacheKey = req.method + path; + var tuple = app.isProduction + ? app.handlerCache.putIfAbsent(cacheKey, resolveTuple) + : resolveTuple(); + + req.params.addAll(tuple.item2); + req.inject(ParseResult, tuple.item3); + + if (!app.isProduction && app.logger != null) + req.inject(Stopwatch, new Stopwatch()..start()); + + var pipeline = tuple.item1; + + Future Function() runPipeline; + + for (var handler in pipeline) { + if (handler == null) break; + + if (runPipeline == null) + runPipeline = () => app.executeHandler(handler, req, res); + else { + var current = runPipeline; + runPipeline = () => current().then((result) => !result + ? new Future.value(result) + : app.executeHandler(handler, req, res)); + } + } + + return runPipeline == null + ? sendResponse(req, res) + : runPipeline().then((_) => sendResponse(req, res)); + } + + if (useZone == false) { + return handle().catchError((e, StackTrace st) { + if (e is FormatException) + throw new AngelHttpException.badRequest(message: e.message) + ..stackTrace = st; + throw new AngelHttpException(e, stackTrace: st, statusCode: 500); + }, test: (e) => e is! AngelHttpException).catchError( + (AngelHttpException e, StackTrace st) { + return handleAngelHttpException(e, e.stackTrace ?? st, req, res); + }).whenComplete(() => res.dispose()); + } else { + var zoneSpec = new ZoneSpecification( + print: (self, parent, zone, line) { + if (app.logger != null) + app.logger.info(line); + else + parent.print(zone, line); + }, + handleUncaughtError: (self, parent, zone, error, stackTrace) { + var trace = new Trace.from(stackTrace ?? StackTrace.current).terse; + + return new Future(() { + AngelHttpException e; + + if (error is FormatException) { + e = new AngelHttpException.badRequest(message: error.message); + } else if (error is AngelHttpException) { + e = error; + } else { + e = new AngelHttpException(error, + stackTrace: stackTrace, message: error?.toString()); + } + + if (app.logger != null) { + app.logger.severe(e.message ?? e.toString(), error, trace); + } + + return handleAngelHttpException(e, trace, req, res); + }).catchError((e, StackTrace st) { + var trace = new Trace.from(st ?? StackTrace.current).terse; + WingsResponseContext._closeSocket(req._sockfd); + // Ideally, we won't be in a position where an absolutely fatal error occurs, + // but if so, we'll need to log it. + if (app.logger != null) { + app.logger.severe( + 'Fatal error occurred when processing ${req.uri}.', e, trace); + } else { + stderr + ..writeln('Fatal error occurred when processing ' + '${req.uri}:') + ..writeln(e) + ..writeln(trace); + } + }); + }, + ); + + var zone = Zone.current.fork(specification: zoneSpec); + req.inject(Zone, zone); + req.inject(ZoneSpecification, zoneSpec); + return zone.run(handle).whenComplete(() { + res.dispose(); + }); + } + } + + /// Handles an [AngelHttpException]. + Future handleAngelHttpException(AngelHttpException e, StackTrace st, + WingsRequestContext req, WingsResponseContext res, + {bool ignoreFinalizers: false}) { + if (req == null || res == null) { + try { + app.logger?.severe(e, st); + var b = new StringBuffer(); + b.writeln('HTTP/1.1 500 Internal Server Error'); + b.writeln(); + + WingsResponseContext._send( + req._sockfd, _coerceUint8List(b.toString().codeUnits)); + WingsResponseContext._closeSocket(req._sockfd); + } finally { + return null; + } + } + + Future handleError; + + if (!res.isOpen) + handleError = new Future.value(); + else { + res.statusCode = e.statusCode; + handleError = + new Future.sync(() => app.errorHandler(e, req, res)).then((result) { + return app.executeHandler(result, req, res).then((_) => res.end()); + }); + } + + return handleError.then((_) => + sendResponse(req, res, ignoreFinalizers: ignoreFinalizers == true)); + } + + /// Sends a response. + Future sendResponse(WingsRequestContext req, WingsResponseContext res, + {bool ignoreFinalizers: false}) { + if (res.willCloseItself) return new Future.value(); + + Future finalizers = ignoreFinalizers == true + ? new Future.value() + : app.responseFinalizers.fold( + new Future.value(), (out, f) => out.then((_) => f(req, res))); + + if (res.isOpen) res.end(); + + var headers = {}; + headers.addAll(res.headers); + + headers['content-length'] = res.buffer.length.toString(); + + // Ignore chunked transfer encoding + //request.response.headers.chunkedTransferEncoding = res.chunked ?? true; + // TODO: Is there a need to support this? + + List outputBuffer = res.buffer.toBytes(); + + if (res.encoders.isNotEmpty) { + var allowedEncodings = req.headers + .value('accept-encoding') + ?.split(',') + ?.map((s) => s.trim()) + ?.where((s) => s.isNotEmpty) + ?.map((str) { + // Ignore quality specifications in accept-encoding + // ex. gzip;q=0.8 + if (!str.contains(';')) return str; + return str.split(';')[0]; + }); + + if (allowedEncodings != null) { + for (var encodingName in allowedEncodings) { + Converter, List> encoder; + String key = encodingName; + + if (res.encoders.containsKey(encodingName)) + encoder = res.encoders[encodingName]; + else if (encodingName == '*') { + encoder = res.encoders[key = res.encoders.keys.first]; + } + + if (encoder != null) { + headers['content-encoding'] = key; + outputBuffer = res.encoders[key].convert(outputBuffer); + + headers['content-length'] = outputBuffer.length.toString(); + break; + } + } + } + } + + var b = new StringBuffer(); + b.writeln('HTTP/1.1 ${res.statusCode}'); + + res.headers.forEach((k, v) { + b.writeln('$k: $v'); + }); + + // Persist session ID + if (res.correspondingRequest._session != null) { + res.cookies + .add(new Cookie('DARTSESSID', res.correspondingRequest._session.id)); + } + + // Send all cookies + for (var cookie in res.cookies) { + var value = cookie.toString(); + b.writeln('set-cookie: $value'); + } + + b.writeln(); + + var buf = new Uint8List.fromList( + new List.from(b.toString().codeUnits)..addAll(outputBuffer)); + + return finalizers.then((_) { + WingsResponseContext._send(req._sockfd, buf); + WingsResponseContext._closeSocket(req._sockfd); + + if (req.injections.containsKey(PoolResource)) { + req.injections[PoolResource].release(); + } + + if (!app.isProduction && app.logger != null) { + var sw = req.grab(Stopwatch); + + if (sw.isRunning) { + sw?.stop(); + app.logger.info("${res.statusCode} ${req.method} ${req.uri} (${sw + ?.elapsedMilliseconds ?? 'unknown'} ms)"); + } + } + }); + } +} diff --git a/lib/src/wings.h b/lib/src/wings.h new file mode 100644 index 00000000..da4f06c8 --- /dev/null +++ b/lib/src/wings.h @@ -0,0 +1,45 @@ +#ifndef ANGEL_WINGS_H +#define ANGEL_WINGS_H +#ifndef WIN32 +#include +#include +#include +#include +#else +#include +#include +#include +#include +// Need to link with Ws2_32.lib, Mswsock.lib, and Advapi32.lib +#pragma comment(lib, "Ws2_32.lib") +#pragma comment(lib, "Mswsock.lib") +#pragma comment(lib, "AdvApi32.lib") +#endif +#include +#include +#include +#include +#include + +class WingsServerInfo +{ +public: + std::mutex mutex; + std::string addressString; + uint64_t port; + int sockfd; + bool ipv6; +}; + +extern std::mutex serverInfoVectorMutex; +extern std::vector serverInfoVector; + +Dart_Handle HandleError(Dart_Handle handle); + +void wings_AddressToString(Dart_NativeArguments arguments); +void wings_BindSocket(Dart_NativeArguments arguments); +void wings_CloseSocket(Dart_NativeArguments arguments); +void wings_Send(Dart_NativeArguments arguments); +void wings_StartHttpListener(Dart_NativeArguments arguments); + +#endif \ No newline at end of file diff --git a/lib/src/wings_request.dart b/lib/src/wings_request.dart new file mode 100644 index 00000000..d58b9505 --- /dev/null +++ b/lib/src/wings_request.dart @@ -0,0 +1,157 @@ +part of angel_wings; + +class WingsRequestContext extends RequestContext { + final AngelWings _wings; + final int _sockfd; + + @override + Angel app; + + WingsRequestContext._(this._wings, this._sockfd, Angel app) : this.app = app; + + static final RegExp _straySlashes = new RegExp(r'(^/+)|(/+$)'); + + final Map _headers = {}; + + String __contentTypeString; + String __path; + String __url; + + Uint8List _addressBytes; + StreamController _body; + ContentType _contentType; + List _cookies; + String _headerField, _hostname, _originalMethod, _method, _path; + HttpHeaders _httpHeaders; + InternetAddress _remoteAddress; + HttpSession _session; + Uri _uri; + + static String _addressToString(Uint8List bytes, bool ipV6) + native "AddressToString"; + + String get _contentTypeString => + __contentTypeString ??= _headers['content-type']?.toString(); + + void set _headerValue(String value) { + if (_headerField != null) { + _headers[_headerField.toLowerCase()] = value; + _headerField = null; + } + } + + @override + ContentType get contentType => _contentType ??= (_contentTypeString == null + ? ContentType.binary + : ContentType.parse(_contentTypeString)); + + @override + List get cookies { + if (_cookies != null) { + return _cookies; + } + + var cookies = []; + + return _cookies = new List.unmodifiable(cookies); + } + + @override + HttpHeaders get headers => _httpHeaders ??= new _WingsIncomingHeaders(this); + + @override + String get hostname => _hostname ??= + (_headers['host'] ?? '${_wings.address.address}:${_wings.port}'); + + @override + HttpRequest get io => null; + + @override + String get method => + _method ??= (_headers['x-http-method-override'] ?? originalMethod); + + @override + String get originalMethod => _originalMethod; + + @override + Future parseOnce() { + return parseBodyFromStream( + _body?.stream ?? new Stream>.empty(), + contentType == null ? null : new MediaType.parse(contentType.toString()), + uri, + storeOriginalBuffer: app.storeOriginalBuffer, + ); + } + + @override + String get path { + if (_path != null) { + return __path; + } else { + var path = __path.replaceAll(_straySlashes, ''); + if (path.isEmpty) path = '/'; + return _path = path; + } + } + + @override + InternetAddress get remoteAddress => _remoteAddress ??= new InternetAddress( + _addressToString(_addressBytes, _addressBytes.length > 4)); + + @override + HttpSession get session { + if (_session != null) return _session; + var dartSessIdCookie = cookies.firstWhere((c) => c.name == 'DARTSESSID', + orElse: () => new Cookie('DARTSESSID', _wings._uuid.v4().toString())); + return _session = _wings._sessions.putIfAbsent(dartSessIdCookie.value, + () => new MockHttpSession(id: dartSessIdCookie.value)); + } + + @override + Uri get uri => _uri ??= Uri.parse(__url); + + @override + bool get xhr => + _headers['x-requested-with']?.trim()?.toLowerCase() == 'xmlhttprequest'; +} + +class _WingsIncomingHeaders extends HttpHeaders { + final WingsRequestContext request; + + _WingsIncomingHeaders(this.request); + + UnsupportedError _unsupported() => + new UnsupportedError('Cannot modify incoming HTTP headers.'); + + @override + List operator [](String name) { + return value(name)?.split(',')?.map((s) => s.trim())?.toList(); + } + + @override + void add(String name, Object value) => throw _unsupported(); + + @override + void clear() => throw _unsupported(); + + @override + void forEach(void Function(String name, List values) f) { + request._headers.forEach((name, value) => + f(name, value.split(',').map((s) => s.trim()).toList())); + } + + @override + void noFolding(String name) => throw _unsupported(); + + @override + void remove(String name, Object value) => throw _unsupported(); + + @override + void removeAll(String name) => throw _unsupported(); + + @override + void set(String name, Object value) => throw _unsupported(); + + @override + String value(String name) => request._headers[name.toLowerCase()]; +} diff --git a/lib/src/wings_response.dart b/lib/src/wings_response.dart new file mode 100644 index 00000000..47abec7b --- /dev/null +++ b/lib/src/wings_response.dart @@ -0,0 +1,158 @@ +part of angel_wings; + +class WingsResponseContext extends ResponseContext { + final WingsRequestContext correspondingRequest; + bool _isClosed = false, _useStream = false; + + WingsResponseContext._(this.correspondingRequest); + + static void _send(int sockfd, Uint8List data) native "Send"; + + static void _closeSocket(int sockfd) native "CloseSocket"; + + @override + void add(List data) { + if (_isClosed && !_useStream) + throw ResponseContext.closed(); + else if (_useStream) + _send(correspondingRequest._sockfd, _coerceUint8List(data)); + else + buffer.add(data); + } + + @override + Future close() { + _closeSocket(correspondingRequest._sockfd); + _isClosed = true; + _useStream = false; + return super.close(); + } + + @override + void end() { + _isClosed = true; + super.end(); + } + + @override + Future addStream(Stream> stream) { + if (_isClosed && !_useStream) throw ResponseContext.closed(); + var firstStream = useStream(); + + Stream> output = stream; + + if ((firstStream || !headers.containsKey('content-encoding')) && + encoders.isNotEmpty && + correspondingRequest != null) { + var allowedEncodings = + (correspondingRequest.headers['accept-encoding'] ?? []).map((str) { + // Ignore quality specifications in accept-encoding + // ex. gzip;q=0.8 + if (!str.contains(';')) return str; + return str.split(';')[0]; + }); + + for (var encodingName in allowedEncodings) { + Converter, List> encoder; + String key = encodingName; + + if (encoders.containsKey(encodingName)) + encoder = encoders[encodingName]; + else if (encodingName == '*') { + encoder = encoders[key = encoders.keys.first]; + } + + if (encoder != null) { + /* + if (firstStream) { + this.stream.sendHeaders([ + new Header.ascii( + 'content-encoding', headers['content-encoding'] = key) + ]); + } + */ + + output = encoders[key].bind(output); + break; + } + } + } + + return output.forEach(((data) => + _send(correspondingRequest._sockfd, _coerceUint8List(data)))); + } + + @override + HttpResponse get io => null; + + @override + bool get isOpen => !_isClosed; + + @override + bool get streaming => _useStream; + + @override + bool useStream() { + if (!_useStream) { + // If this is the first stream added to this response, + // then add headers, status code, etc. + _finalize(); + + willCloseItself = _useStream = _isClosed = true; + releaseCorrespondingRequest(); + return true; + } + + return false; + } + + /// Write headers, status, etc. to the underlying [stream]. + void _finalize() { + var b = new StringBuffer(); + b.writeln('HTTP/1.1 $statusCode'); + headers['date'] ??= HttpDate.format(new DateTime.now()); + + if (encoders.isNotEmpty && correspondingRequest != null) { + var allowedEncodings = + (correspondingRequest.headers['accept-encoding'] ?? []).map((str) { + // Ignore quality specifications in accept-encoding + // ex. gzip;q=0.8 + if (!str.contains(';')) return str; + return str.split(';')[0]; + }); + + for (var encodingName in allowedEncodings) { + String key = encodingName; + + if (encoders.containsKey(encodingName)) { + this.headers['content-encoding'] = key; + break; + } + } + } + + // Add all normal headers + this.headers.forEach((k, v) { + b.writeln('$k: $v'); + }); + + // Persist session ID + if (correspondingRequest._session != null) { + cookies.add(new Cookie('DARTSESSID', correspondingRequest._session.id)); + } + + // Send all cookies + for (var cookie in cookies) { + var value = cookie.toString(); + b.writeln('set-cookie: $value'); + } + + b.writeln(); + + _send( + correspondingRequest._sockfd, _coerceUint8List(b.toString().codeUnits)); + } +} + +Uint8List _coerceUint8List(List list) => + list is Uint8List ? list : new Uint8List.fromList(list); diff --git a/lib/src/wings_thread.h b/lib/src/wings_thread.h new file mode 100644 index 00000000..b86fd2f1 --- /dev/null +++ b/lib/src/wings_thread.h @@ -0,0 +1,25 @@ +#ifndef ANGEL_WINGS_THREAD_H +#define ANGEL_WINGS_THREAD_H +#include +#include +#include "wings.h" + +typedef struct +{ + Dart_Port port; + WingsServerInfo *serverInfo; +} wings_thread_info; + +typedef struct +{ + bool ipv6; + int sock; + sockaddr addr; + socklen_t addr_len; + Dart_Port port; +} requestInfo; + +void wingsThreadMain(wings_thread_info *info); +void handleRequest(requestInfo *rq); + +#endif \ No newline at end of file diff --git a/lib/src/worker_thread.cc b/lib/src/worker_thread.cc new file mode 100644 index 00000000..fbe0ee15 --- /dev/null +++ b/lib/src/worker_thread.cc @@ -0,0 +1,215 @@ +#include +#include +#include "wings_thread.h" + +void wingsThreadMain(wings_thread_info *info) +{ + auto *serverInfo = std::move(info->serverInfo); + Dart_Port port = std::move(info->port); + //delete info; + + while (true) + { + std::lock_guard lock(serverInfo->mutex); + + sockaddr client_addr{}; + socklen_t client_addr_len; + int client = accept(serverInfo->sockfd, &client_addr, &client_addr_len); + + if (client < 0) + { + // send_error(info->port, "Failed to accept client socket."); + return; + } + + requestInfo rq{}; + rq.ipv6 = serverInfo->ipv6; + rq.sock = client; + rq.addr = client_addr; + rq.addr_len = client_addr_len; + rq.port = port; + handleRequest(&rq); + } +} + +int send_notification(http_parser *parser, int code) +{ + //if (parser == nullptr) return 0; + auto *rq = (requestInfo *)parser->data; + //if (rq == nullptr) return 0; + + Dart_CObject first{}; + Dart_CObject second{}; + first.type = second.type = Dart_CObject_kInt64; + first.value.as_int64 = rq->sock; + second.value.as_int64 = code; + + Dart_CObject *list[2]{&first, &second}; + Dart_CObject obj{}; + obj.type = Dart_CObject_kArray; + obj.value.as_array.length = 2; + obj.value.as_array.values = list; + Dart_PostCObject(rq->port, &obj); + return 0; +} + +int send_string(http_parser *parser, char *str, size_t length, int code, bool as_typed_data = false) +{ + //if (parser == nullptr) return 0; + auto *rq = (requestInfo *)parser->data; + //if (rq == nullptr) return 0; + auto *s = new char[length + 1]; + memset(s, 0, length + 1); + + Dart_CObject first{}; + Dart_CObject second{}; + Dart_CObject third{}; + first.type = second.type = Dart_CObject_kInt32; + first.value.as_int32 = rq->sock; + second.value.as_int32 = code; + + if (!as_typed_data) + { + third.type = Dart_CObject_kString; + memcpy(s, str, length); + third.value.as_string = s; + } + else + { + third.type = Dart_CObject_kExternalTypedData; + third.type = Dart_CObject_kExternalTypedData; + third.value.as_external_typed_data.type = Dart_TypedData_kUint8; + third.value.as_external_typed_data.length = length; + third.value.as_external_typed_data.data = (uint8_t *)str; + } + + // Post the string back to Dart... + Dart_CObject *list[3]{&first, &second, &third}; + Dart_CObject obj{}; + obj.type = Dart_CObject_kArray; + obj.value.as_array.length = 3; + obj.value.as_array.values = list; + Dart_PostCObject(rq->port, &obj); + delete[] s; + return 0; +} + +int send_oncomplete(http_parser *parser, int code) +{ + //if (parser == nullptr) return 0; + auto *rq = (requestInfo *)parser->data; + //if (rq == nullptr) return 0; + + Dart_CObject sockfd{}; + Dart_CObject command{}; + Dart_CObject method{}; + Dart_CObject major{}; + Dart_CObject minor{}; + Dart_CObject addr{}; + sockfd.type = command.type = method.type = major.type = minor.type = Dart_CObject_kInt32; + addr.type = Dart_CObject_kExternalTypedData; + sockfd.value.as_int32 = rq->sock; + command.value.as_int32 = code; + method.value.as_int32 = parser->method; + major.value.as_int32 = parser->http_major; + minor.value.as_int32 = parser->http_minor; + addr.value.as_external_typed_data.type = Dart_TypedData_kUint8; + addr.value.as_external_typed_data.length = rq->addr_len; + + if (rq->ipv6) + { + auto *v6 = (sockaddr_in6 *)&rq->addr; + addr.value.as_external_typed_data.data = (uint8_t *)v6->sin6_addr.s6_addr; + } + else + { + auto *v4 = (sockaddr_in *)&rq->addr; + addr.value.as_external_typed_data.data = (uint8_t *)&v4->sin_addr.s_addr; + } + + Dart_CObject *list[6]{&sockfd, &command, &method, &major, &minor, &addr}; + Dart_CObject obj{}; + obj.type = Dart_CObject_kArray; + obj.value.as_array.length = 6; + obj.value.as_array.values = list; + Dart_PostCObject(rq->port, &obj); + //delete parser; + return 0; +} + +void handleRequest(requestInfo *rq) +{ + size_t len = 80 * 1024, nparsed; + char buf[len]; + ssize_t recved; + memset(buf, 0, len); + + http_parser parser{}; + http_parser_init(&parser, HTTP_REQUEST); + parser.data = rq; //rq.get(); + + http_parser_settings settings{}; + + settings.on_message_begin = [](http_parser *parser) { + // std::cout << "mb" << std::endl; + return send_notification(parser, 0); + }; + + settings.on_message_complete = [](http_parser *parser) { + //std::cout << "mc" << std::endl; + send_oncomplete(parser, 1); + //delete (requestInfo *) parser->data; + //std::cout << "deleted rq!" << std::endl; + return 0; + }; + + settings.on_url = [](http_parser *parser, const char *at, size_t length) { + // std::cout << "url" << std::endl; + return send_string(parser, (char *)at, length, 2); + }; + + settings.on_header_field = [](http_parser *parser, const char *at, size_t length) { + // std::cout << "hf" << std::endl; + return send_string(parser, (char *)at, length, 3); + }; + + settings.on_header_value = [](http_parser *parser, const char *at, size_t length) { + // std::cout << "hv" << std::endl; + return send_string(parser, (char *)at, length, 4); + }; + + settings.on_body = [](http_parser *parser, const char *at, size_t length) { + // std::cout << "body" << std::endl; + return send_string(parser, (char *)at, length, 5, true); + }; + + unsigned int isUpgrade = 0; + + // std::cout << "start" << std::endl; + while ((recved = recv(rq->sock, buf, len, 0)) > 0) + { + if (isUpgrade) + { + send_string(&parser, buf, (size_t)recved, 7, true); + } + else + { + /* Start up / continue the parser. + * Note we pass recved==0 to signal that EOF has been received. + */ + nparsed = http_parser_execute(&parser, &settings, buf, (size_t)recved); + + if ((isUpgrade = parser.upgrade) == 1) + { + send_notification(&parser, 6); + } + else if (nparsed != recved) + { + close(rq->sock); + return; + } + } + + memset(buf, 0, len); + } +} \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 00000000..90b0a776 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,13 @@ +name: angel_wings +dependencies: + angel_framework: ^1.0.0 + build_native: ^0.0.9 + mock_request: ^1.0.0 + pooled_map: ^1.0.0 + uuid: ^1.0.0 +dev_dependencies: + build_runner: + git: + url: https://github.com/thosakwe/build.git + path: build_runner + ref: experimental-hot-reloading \ No newline at end of file From 35e019dbef0f3cc4734eeb3e81ecc409e646ff89 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Sun, 8 Jul 2018 18:59:51 -0400 Subject: [PATCH 02/28] Attempting to diagnose NPE --- .gitignore | 6 ++- Makefile | 46 ++++++++++++++++++ example/main.dart | 12 ++++- lib/angel_wings.dart | 1 + lib/src/bind_socket.cc | 10 ++-- lib/src/dart_debug.h | 27 +++++++++++ lib/src/http_listener.cc | 19 ++++++++ lib/src/send.cc | 8 ++++ lib/src/wings.cc | 8 +++- lib/src/wings.dart | 96 ++++++++++++++++++++++++++----------- lib/src/wings_request.dart | 7 +-- lib/src/wings_response.dart | 12 ++--- lib/src/worker_thread.cc | 38 +++++++++------ 13 files changed, 230 insertions(+), 60 deletions(-) create mode 100644 Makefile create mode 100644 lib/src/dart_debug.h diff --git a/.gitignore b/.gitignore index 2e7f80df..b87ee36c 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,8 @@ doc/api/ # project includes source files written in JavaScript. *.js_ *.js.deps -*.js.map \ No newline at end of file +*.js.map + +*.o +*.dylib +*.a \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..33a6dc20 --- /dev/null +++ b/Makefile @@ -0,0 +1,46 @@ +CXX=clang +HTTP_PARSER=.dart_tool/build_native/third_party/angel_wings.http_parser +CXX_INCLUDES=-I$(HTTP_PARSER) -I$(DART_SDK)/include + +.PHONY: clean debug + +clean: + find lib -name "*.a" -delete + find lib -name "*.o" -delete + find lib -name "*.dylib" -delete + +debug: + $(MAKE) lib/src/libwings.dylib CXXFLAGS="-g -DDEBUG=1" + +example: debug + lldb -o "target create dart" \ + -o "process launch --stop-at-entry example/main.dart" \ + -o "process handle SIGINT -p true" \ + -o "continue" \ + +lib/src/bind_socket.o: + $(CXX) $(CXXFLAGS) $(CXX_INCLUDES) -std=c++11 -c -o lib/src/bind_socket.o lib/src/bind_socket.cc + +lib/src/http_listener.o: + $(CXX) $(CXXFLAGS) $(CXX_INCLUDES) -std=c++11 -c -o lib/src/http_listener.o lib/src/http_listener.cc + +lib/src/http_parser.o: + $(CXX) $(CXXFLAGS) $(CXX_INCLUDES) -c -o lib/src/http_parser.o $(HTTP_PARSER)/http_parser.c + +lib/src/send.o: + $(CXX) $(CXXFLAGS) $(CXX_INCLUDES) -std=c++11 -c -o lib/src/send.o lib/src/send.cc + +lib/src/util.o: + $(CXX) $(CXXFLAGS) $(CXX_INCLUDES) -std=c++11 -c -o lib/src/util.o lib/src/util.cc + +lib/src/wings.o: + $(CXX) $(CXXFLAGS) $(CXX_INCLUDES) -std=c++11 -c -o lib/src/wings.o lib/src/wings.cc + +lib/src/worker_thread.o: + $(CXX) $(CXXFLAGS) $(CXX_INCLUDES) -std=c++11 -c -o lib/src/worker_thread.o lib/src/worker_thread.cc + +lib/src/libwings.dylib: lib/src/bind_socket.o lib/src/http_listener.o lib/src/http_parser.o lib/src/send.o lib/src/util.o lib/src/wings.o lib/src/worker_thread.o + $(CXX) $(CXXFLAGS) -shared -o lib/src/libwings.dylib -undefined dynamic_lookup -DDART_SHARED_LIB -Wl -fPIC -m64 \ + lib/src/bind_socket.o lib/src/http_listener.o \ + lib/src/http_parser.o lib/src/send.o lib/src/util.o \ + lib/src/wings.o lib/src/worker_thread.o \ No newline at end of file diff --git a/example/main.dart b/example/main.dart index 8d0f950b..204795ae 100644 --- a/example/main.dart +++ b/example/main.dart @@ -5,6 +5,7 @@ import 'package:angel_framework/angel_framework.dart'; import 'package:angel_wings/angel_wings.dart'; main() async { + if (false) for (int i = 1; i < Platform.numberOfProcessors; i++) { var onError = new ReceivePort(); Isolate.spawn(isolateMain, i, onError: onError.sendPort); @@ -17,9 +18,16 @@ main() async { void isolateMain(int id) { var app = new Angel(); - var wings = new AngelWings(app, shared: true); + var wings = new AngelWings(app, shared: id > 0, useZone: false); - app.get('/', 'Hello, native world!'); + var old = app.errorHandler; + app.errorHandler = (e, req, res) { + print(e); + print(e.stackTrace); + return old(e, req, res); + }; + + app.get('/', 'Hello, native world! This is isolate #$id.'); wings.startServer('127.0.0.1', 3000).then((_) { print( diff --git a/lib/angel_wings.dart b/lib/angel_wings.dart index 5fb2763a..7029acd0 100644 --- a/lib/angel_wings.dart +++ b/lib/angel_wings.dart @@ -10,6 +10,7 @@ import 'package:angel_framework/angel_framework.dart'; import 'package:body_parser/body_parser.dart'; import 'package:combinator/combinator.dart'; import 'package:http_parser/http_parser.dart'; +import 'package:json_god/json_god.dart' as god; import 'package:mock_request/mock_request.dart'; import 'package:pool/pool.dart'; import 'package:pooled_map/pooled_map.dart'; diff --git a/lib/src/bind_socket.cc b/lib/src/bind_socket.cc index 995ef857..fa077c90 100644 --- a/lib/src/bind_socket.cc +++ b/lib/src/bind_socket.cc @@ -34,7 +34,11 @@ void wings_BindSocket(Dart_NativeArguments arguments) std::string addressStringInstance(addressString); std::lock_guard lock(serverInfoVectorMutex); +#if __APPLE__ + if (false) +#else if (shared) +#endif { for (unsigned long i = 0; i < serverInfoVector.size(); i++) { @@ -42,7 +46,7 @@ void wings_BindSocket(Dart_NativeArguments arguments) if (server_info->addressString == addressStringInstance && server_info->port == port) { - existingIndex = (long) i; + existingIndex = (long)i; break; } } @@ -113,7 +117,7 @@ void wings_BindSocket(Dart_NativeArguments arguments) return; } - /* +#if __APPLE__ ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &i, sizeof(i)); if (ret < 0) @@ -121,7 +125,7 @@ void wings_BindSocket(Dart_NativeArguments arguments) Dart_ThrowException(Dart_NewStringFromCString("Cannot reuse port for socket.")); return; } - */ +#endif if (addressLength > 4) { diff --git a/lib/src/dart_debug.h b/lib/src/dart_debug.h new file mode 100644 index 00000000..90789d40 --- /dev/null +++ b/lib/src/dart_debug.h @@ -0,0 +1,27 @@ +#ifdef __cplusplus +#include +#else +#include +#endif +#include + +Dart_Handle ToCString(Dart_Handle obj, const char** out) { + Dart_Handle toStringMethod = Dart_NewStringFromCString("toString"); + Dart_Handle string = Dart_Invoke(obj, toStringMethod, 0, nullptr); + return Dart_StringToCString(string, out); +} + +Dart_Handle Dart_PrintToFile(Dart_Handle obj, FILE* stream) { + const char *toString; + Dart_Handle result = ToCString(obj, &toString); + + if (Dart_IsError(result)) + return result; + + fprintf(stream, "%s\n", toString); + return Dart_Null(); +} + +Dart_Handle Dart_Print(Dart_Handle obj) { + return Dart_PrintToFile(obj, stdout); +} \ No newline at end of file diff --git a/lib/src/http_listener.cc b/lib/src/http_listener.cc index 534466b6..c5a74d4c 100644 --- a/lib/src/http_listener.cc +++ b/lib/src/http_listener.cc @@ -1,5 +1,6 @@ #include #include +#include #include "wings.h" #include "wings_thread.h" @@ -47,4 +48,22 @@ void handleMessage(Dart_Port destPortId, Dart_CObject *message) Dart_Port port = message->value.as_array.values[1]->value.as_send_port.id; Dart_CloseNativePort(port); } + else + { + // This is either a send or close message. + int sockfd = (int)get_int(message->value.as_array.values[0]); + printf("FD: %d\n", sockfd); + + if (message->value.as_array.length == 2) + { + auto *msg = message->value.as_array.values[1]; + printf("Length: %ld\n", msg->value.as_typed_data.length); + write(sockfd, msg->value.as_typed_data.values, (size_t)msg->value.as_typed_data.length); + } + else + { + printf("Close!\n"); + close(sockfd); + } + } } \ No newline at end of file diff --git a/lib/src/send.cc b/lib/src/send.cc index 45c0414d..cfcd222e 100644 --- a/lib/src/send.cc +++ b/lib/src/send.cc @@ -1,3 +1,6 @@ +#ifdef DEBUG +#include "dart_debug.h" +#endif #include "wings.h" void wings_CloseSocket(Dart_NativeArguments arguments) @@ -16,7 +19,12 @@ void wings_Send(Dart_NativeArguments arguments) Dart_TypedData_Type dataType; void *dataBytes; intptr_t dataLength; + +#ifdef DEBUG + HandleError(Dart_Print(sockfdHandle)); +#endif HandleError(Dart_IntegerToUint64(sockfdHandle, &sockfd)); HandleError(Dart_TypedDataAcquireData(dataHandle, &dataType, &dataBytes, &dataLength)); write((int)sockfd, dataBytes, (size_t)dataLength); + HandleError(Dart_TypedDataReleaseData(dataHandle)); } \ No newline at end of file diff --git a/lib/src/wings.cc b/lib/src/wings.cc index 41ab5b15..f884e82b 100644 --- a/lib/src/wings.cc +++ b/lib/src/wings.cc @@ -1,5 +1,5 @@ #include -#include +//#include #include #include #include "wings.h" @@ -25,7 +25,13 @@ DART_EXPORT Dart_Handle wings_Init(Dart_Handle parent_library) Dart_Handle HandleError(Dart_Handle handle) { if (Dart_IsError(handle)) + { +#ifdef DEBUG + Dart_DumpNativeStackTrace(NULL); +#endif Dart_PropagateError(handle); + } + return handle; } diff --git a/lib/src/wings.dart b/lib/src/wings.dart index 67faf25a..d1d90ab2 100644 --- a/lib/src/wings.dart +++ b/lib/src/wings.dart @@ -76,8 +76,9 @@ class AngelWings { final RawReceivePort _recv = new RawReceivePort(); final Map _sessions = {}; - final PooledMap _staging = - new PooledMap(); + final Map _staging = {}; + //final PooledMap _staging = + // new PooledMap(); final Uuid _uuid = new Uuid(); InternetAddress _address; int _port; @@ -88,6 +89,32 @@ class AngelWings { static SendPort _startHttpListener() native "StartHttpListener"; + final Pool _pool = new Pool(1); + + static void __send(int sockfd, Uint8List data) native "Send"; + + static void __closeSocket(int sockfd) native "CloseSocket"; + + void _send(int sockfd, Uint8List data) { + // _pool.withResource(() { + print('Sending ${[sockfd, data]}'); + _sendPort.send([sockfd, data]); + //}); + //_pool.withResource(() => __send(sockfd, data)); + } + + void _closeSocket(WingsRequestContext req) { + //_pool.withResource(() { + if (!req._closed) { + req._closed = true; + var sockfd = req._sockfd; + print('Sending ${[sockfd]}'); + _sendPort.send([sockfd]); + } + //}); + //_pool.withResource(() => __closeSocket(sockfd)); + } + AngelWings(this.app, {this.shared: false, this.useZone: true}) { _recv.handler = _handleMessage; } @@ -137,34 +164,37 @@ class AngelWings { } void _handleMessage(x) { + print('INPUT: $x'); if (x is String) { close(); throw new StateError(x); } else if (x is List && x.length >= 2) { int sockfd = x[0], command = x[1]; - - WingsRequestContext _newRequest() => - new WingsRequestContext._(this, sockfd, app); + + //WingsRequestContext _newRequest() => + // new WingsRequestContext._(this, sockfd, app); //print(x); switch (command) { case messageBegin: - _staging.putIfAbsent(sockfd, _newRequest); + print('BEGIN $sockfd'); + _staging[sockfd] = new WingsRequestContext._(this, sockfd, app); break; case messageComplete: - // (sockfd, method, major, minor, addrBytes) - _staging.update(sockfd, (rq) { + print('$sockfd in $_staging???'); + var rq = _staging.remove(sockfd); + if (rq != null) { rq._method = methodToString(x[2] as int); rq._addressBytes = x[5] as Uint8List; - return rq; - }, defaultValue: _newRequest).then(_handleRequest); + _handleRequest(rq); + } break; case body: - _staging.update(sockfd, (rq) { + var rq = _staging[sockfd]; + if (rq != null) { (rq._body ??= new StreamController()) .add(x[2] as Uint8List); - return rq; - }, defaultValue: _newRequest); + } break; //case upgrade: // TODO: Handle WebSockets...? @@ -175,27 +205,26 @@ class AngelWings { // onUpgradedMessage(sockfd, x[2]); // break; case url: - _staging.update(sockfd, (rq) => rq..__url = x[2] as String, - defaultValue: _newRequest); + _staging[sockfd]?.__url = x[2] as String; break; case headerField: - _staging.update(sockfd, (rq) => rq.._headerField = x[2] as String, - defaultValue: _newRequest); + _staging[sockfd]?._headerField = x[2] as String; break; case headerValue: - _staging.update(sockfd, (rq) => rq.._headerValue = x[2] as String, - defaultValue: _newRequest); + _staging[sockfd]?._headerValue = x[2] as String; break; } } } Future _handleRequest(WingsRequestContext req) { + print('req: $req'); if (req == null) return new Future.value(); var res = new WingsResponseContext._(req) ..app = app - ..serializer = app.serializer + ..serializer = (app.serializer ?? god.serialize) ..encoders.addAll(app.encoders); + print('Handling fd: ${req._sockfd}'); handle() { var path = req.path; @@ -228,6 +257,7 @@ class AngelWings { Future Function() runPipeline; + print('Pipeline: $pipeline'); for (var handler in pipeline) { if (handler == null) break; @@ -286,7 +316,7 @@ class AngelWings { return handleAngelHttpException(e, trace, req, res); }).catchError((e, StackTrace st) { var trace = new Trace.from(st ?? StackTrace.current).terse; - WingsResponseContext._closeSocket(req._sockfd); + _closeSocket(req); // Ideally, we won't be in a position where an absolutely fatal error occurs, // but if so, we'll need to log it. if (app.logger != null) { @@ -323,9 +353,8 @@ class AngelWings { b.writeln('HTTP/1.1 500 Internal Server Error'); b.writeln(); - WingsResponseContext._send( - req._sockfd, _coerceUint8List(b.toString().codeUnits)); - WingsResponseContext._closeSocket(req._sockfd); + _send(req._sockfd, _coerceUint8List(b.toString().codeUnits)); + _closeSocket(req); } finally { return null; } @@ -350,7 +379,9 @@ class AngelWings { /// Sends a response. Future sendResponse(WingsRequestContext req, WingsResponseContext res, {bool ignoreFinalizers: false}) { + print('Closing: ${req._sockfd}'); if (res.willCloseItself) return new Future.value(); + print('Not self-closing: ${req._sockfd}'); Future finalizers = ignoreFinalizers == true ? new Future.value() @@ -368,6 +399,7 @@ class AngelWings { //request.response.headers.chunkedTransferEncoding = res.chunked ?? true; // TODO: Is there a need to support this? + print('Buffer: ${res.buffer}'); List outputBuffer = res.buffer.toBytes(); if (res.encoders.isNotEmpty) { @@ -405,6 +437,7 @@ class AngelWings { } } + print('Create string buffer'); var b = new StringBuffer(); b.writeln('HTTP/1.1 ${res.statusCode}'); @@ -425,13 +458,20 @@ class AngelWings { } b.writeln(); + print(b); - var buf = new Uint8List.fromList( - new List.from(b.toString().codeUnits)..addAll(outputBuffer)); + var bb = new BytesBuilder(copy: false) + ..add(b.toString().codeUnits) + ..add(outputBuffer); + var buf = _coerceUint8List(bb.takeBytes()); + print('Output: $buf'); return finalizers.then((_) { - WingsResponseContext._send(req._sockfd, buf); - WingsResponseContext._closeSocket(req._sockfd); + print('A'); + _send(req._sockfd, buf); + print('B'); + _closeSocket(req); + print('C'); if (req.injections.containsKey(PoolResource)) { req.injections[PoolResource].release(); diff --git a/lib/src/wings_request.dart b/lib/src/wings_request.dart index d58b9505..dc56400b 100644 --- a/lib/src/wings_request.dart +++ b/lib/src/wings_request.dart @@ -3,6 +3,7 @@ part of angel_wings; class WingsRequestContext extends RequestContext { final AngelWings _wings; final int _sockfd; + bool _closed = false; @override Angel app; @@ -14,7 +15,7 @@ class WingsRequestContext extends RequestContext { final Map _headers = {}; String __contentTypeString; - String __path; + //String __path; String __url; Uint8List _addressBytes; @@ -86,9 +87,9 @@ class WingsRequestContext extends RequestContext { @override String get path { if (_path != null) { - return __path; + return _path; } else { - var path = __path.replaceAll(_straySlashes, ''); + var path = __url?.replaceAll(_straySlashes, '') ?? ''; if (path.isEmpty) path = '/'; return _path = path; } diff --git a/lib/src/wings_response.dart b/lib/src/wings_response.dart index 47abec7b..2cc4a258 100644 --- a/lib/src/wings_response.dart +++ b/lib/src/wings_response.dart @@ -6,23 +6,21 @@ class WingsResponseContext extends ResponseContext { WingsResponseContext._(this.correspondingRequest); - static void _send(int sockfd, Uint8List data) native "Send"; - - static void _closeSocket(int sockfd) native "CloseSocket"; + AngelWings get _wings => correspondingRequest._wings; @override void add(List data) { if (_isClosed && !_useStream) throw ResponseContext.closed(); else if (_useStream) - _send(correspondingRequest._sockfd, _coerceUint8List(data)); + _wings._send(correspondingRequest._sockfd, _coerceUint8List(data)); else buffer.add(data); } @override Future close() { - _closeSocket(correspondingRequest._sockfd); + _wings._closeSocket(correspondingRequest); _isClosed = true; _useStream = false; return super.close(); @@ -79,7 +77,7 @@ class WingsResponseContext extends ResponseContext { } return output.forEach(((data) => - _send(correspondingRequest._sockfd, _coerceUint8List(data)))); + _wings._send(correspondingRequest._sockfd, _coerceUint8List(data)))); } @override @@ -149,7 +147,7 @@ class WingsResponseContext extends ResponseContext { b.writeln(); - _send( + _wings._send( correspondingRequest._sockfd, _coerceUint8List(b.toString().codeUnits)); } } diff --git a/lib/src/worker_thread.cc b/lib/src/worker_thread.cc index fbe0ee15..d98790ce 100644 --- a/lib/src/worker_thread.cc +++ b/lib/src/worker_thread.cc @@ -1,3 +1,4 @@ +//#include #include #include #include "wings_thread.h" @@ -10,25 +11,31 @@ void wingsThreadMain(wings_thread_info *info) while (true) { - std::lock_guard lock(serverInfo->mutex); + std::unique_lock lock(serverInfo->mutex, std::defer_lock); sockaddr client_addr{}; socklen_t client_addr_len; - int client = accept(serverInfo->sockfd, &client_addr, &client_addr_len); - if (client < 0) + if (lock.try_lock()) { - // send_error(info->port, "Failed to accept client socket."); - return; - } + int client = accept(serverInfo->sockfd, &client_addr, &client_addr_len); + lock.unlock(); - requestInfo rq{}; - rq.ipv6 = serverInfo->ipv6; - rq.sock = client; - rq.addr = client_addr; - rq.addr_len = client_addr_len; - rq.port = port; - handleRequest(&rq); + if (client < 0) + { + // send_error(info->port, "Failed to accept client socket."); + return; + } + + //auto rq = std::make_shared(); + 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); + } } } @@ -137,7 +144,8 @@ int send_oncomplete(http_parser *parser, int code) return 0; } -void handleRequest(requestInfo *rq) +//void handleRequest(const std::shared_ptr &rq) +void handleRequest(requestInfo* rq) { size_t len = 80 * 1024, nparsed; char buf[len]; @@ -158,7 +166,7 @@ void handleRequest(requestInfo *rq) settings.on_message_complete = [](http_parser *parser) { //std::cout << "mc" << std::endl; send_oncomplete(parser, 1); - //delete (requestInfo *) parser->data; + delete (requestInfo *)parser->data; //std::cout << "deleted rq!" << std::endl; return 0; }; From 311dede079c1d6e09167fd4cbb22a4d8d63eb021 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Sun, 8 Jul 2018 19:36:41 -0400 Subject: [PATCH 03/28] Can't resolve --- lib/src/http_listener.cc | 38 ++++++++++++++++++++++++++++++++++---- lib/src/worker_thread.cc | 28 ++++++++++++++++------------ 2 files changed, 50 insertions(+), 16 deletions(-) diff --git a/lib/src/http_listener.cc b/lib/src/http_listener.cc index c5a74d4c..2c557d4f 100644 --- a/lib/src/http_listener.cc +++ b/lib/src/http_listener.cc @@ -1,5 +1,6 @@ #include #include +#include #include #include "wings.h" #include "wings_thread.h" @@ -29,6 +30,10 @@ int64_t get_int(Dart_CObject *obj) void handleMessage(Dart_Port destPortId, Dart_CObject *message) { + if (message->type != Dart_CObject_kArray) { + return; + } + // We always expect an array to be sent. Dart_CObject_Type firstType = message->value.as_array.values[0]->type; @@ -46,19 +51,44 @@ void handleMessage(Dart_Port destPortId, Dart_CObject *message) { // The Dart world is trying to close this port. Dart_Port port = message->value.as_array.values[1]->value.as_send_port.id; - Dart_CloseNativePort(port); + //Dart_CloseNativePort(port); } else { // This is either a send or close message. + std::cout << "NVALUES: " << message->value.as_array.length << std::endl; int sockfd = (int)get_int(message->value.as_array.values[0]); printf("FD: %d\n", sockfd); - if (message->value.as_array.length == 2) + if (message->value.as_array.length >= 2) { auto *msg = message->value.as_array.values[1]; - printf("Length: %ld\n", msg->value.as_typed_data.length); - write(sockfd, msg->value.as_typed_data.values, (size_t)msg->value.as_typed_data.length); + + if (msg != nullptr) + { + + if (msg->type == Dart_CObject_kExternalTypedData) + { + std::cout << "ext typed data" << std::endl; + printf("Length: %ld\n", msg->value.as_external_typed_data.length); + std::cout << "ptr: " << msg->value.as_typed_data.values << std::endl; + write(sockfd, msg->value.as_external_typed_data.data, (size_t)msg->value.as_external_typed_data.length); + } + else if (msg->type == Dart_CObject_kTypedData) + { + std::cout << "regular typed data" << std::endl; + printf("Length: %ld\n", msg->value.as_typed_data.length); + write(sockfd, msg->value.as_typed_data.values, (size_t)msg->value.as_typed_data.length); + } + else + { + std::cout << "unknown type: " << ((Dart_CObject_Type) msg->type) << std::endl; + } + } + else + { + std::cout << "null msg!!!" << std::endl; + } } else { diff --git a/lib/src/worker_thread.cc b/lib/src/worker_thread.cc index d98790ce..b8ea5b22 100644 --- a/lib/src/worker_thread.cc +++ b/lib/src/worker_thread.cc @@ -1,4 +1,5 @@ //#include +#include #include #include #include "wings_thread.h" @@ -64,9 +65,8 @@ int send_string(http_parser *parser, char *str, size_t length, int code, bool as { //if (parser == nullptr) return 0; auto *rq = (requestInfo *)parser->data; + char *s = nullptr; //if (rq == nullptr) return 0; - auto *s = new char[length + 1]; - memset(s, 0, length + 1); Dart_CObject first{}; Dart_CObject second{}; @@ -77,7 +77,10 @@ int send_string(http_parser *parser, char *str, size_t length, int code, bool as if (!as_typed_data) { + s = new char[length + 1]; + memset(s, 0, length + 1); third.type = Dart_CObject_kString; + //strcpy(s, str); memcpy(s, str, length); third.value.as_string = s; } @@ -97,7 +100,8 @@ int send_string(http_parser *parser, char *str, size_t length, int code, bool as obj.value.as_array.length = 3; obj.value.as_array.values = list; Dart_PostCObject(rq->port, &obj); - delete[] s; + if (s != nullptr) + delete s; return 0; } @@ -145,7 +149,7 @@ int send_oncomplete(http_parser *parser, int code) } //void handleRequest(const std::shared_ptr &rq) -void handleRequest(requestInfo* rq) +void handleRequest(requestInfo *rq) { size_t len = 80 * 1024, nparsed; char buf[len]; @@ -153,21 +157,21 @@ void handleRequest(requestInfo* rq) memset(buf, 0, len); http_parser parser{}; - http_parser_init(&parser, HTTP_REQUEST); parser.data = rq; //rq.get(); + http_parser_init(&parser, HTTP_REQUEST); http_parser_settings settings{}; settings.on_message_begin = [](http_parser *parser) { - // std::cout << "mb" << std::endl; + std::cout << "mb" << std::endl; return send_notification(parser, 0); }; settings.on_message_complete = [](http_parser *parser) { - //std::cout << "mc" << std::endl; + std::cout << "mc" << std::endl; send_oncomplete(parser, 1); delete (requestInfo *)parser->data; - //std::cout << "deleted rq!" << std::endl; + std::cout << "deleted rq!" << std::endl; return 0; }; @@ -177,23 +181,23 @@ void handleRequest(requestInfo* rq) }; settings.on_header_field = [](http_parser *parser, const char *at, size_t length) { - // std::cout << "hf" << std::endl; + std::cout << "hf" << std::endl; return send_string(parser, (char *)at, length, 3); }; settings.on_header_value = [](http_parser *parser, const char *at, size_t length) { - // std::cout << "hv" << std::endl; + std::cout << "hv" << std::endl; return send_string(parser, (char *)at, length, 4); }; settings.on_body = [](http_parser *parser, const char *at, size_t length) { - // std::cout << "body" << std::endl; + std::cout << "body" << std::endl; return send_string(parser, (char *)at, length, 5, true); }; unsigned int isUpgrade = 0; - // std::cout << "start" << std::endl; + std::cout << "start" << std::endl; while ((recved = recv(rq->sock, buf, len, 0)) > 0) { if (isUpgrade) From 2409e06e6af75ec09ac5fa8c21cd29337eb63a64 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Tue, 10 Jul 2018 10:06:51 -0400 Subject: [PATCH 04/28] Fixed EXC_BAD_ACCESS --- Makefile | 11 ++++++++- example/main.dart | 3 +-- lib/angel_wings.dart | 2 +- lib/src/bind_socket.cc | 8 +++---- lib/src/http_listener.cc | 22 +++++++++--------- lib/src/send.cc | 6 ----- lib/src/wings.dart | 45 +++++++++++++++++++------------------ lib/src/wings_response.dart | 8 +++---- lib/src/worker_thread.cc | 39 ++++++++++++++++---------------- pubspec.yaml | 2 +- 10 files changed, 75 insertions(+), 71 deletions(-) diff --git a/Makefile b/Makefile index 33a6dc20..1a49f460 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,13 @@ CXX=clang HTTP_PARSER=.dart_tool/build_native/third_party/angel_wings.http_parser CXX_INCLUDES=-I$(HTTP_PARSER) -I$(DART_SDK)/include -.PHONY: clean debug +.PHONY: clean debug macos all + +all: + printf 'Available targets:\n'\ + ' * `debug` - Builds a debug library on MacOS\n'\ + ' * `example` - Runs example/main.dart in LLDB on MacOS\n'\ + ' * `macos` - Builds a release-mode library on MacOS\n' clean: find lib -name "*.a" -delete @@ -12,6 +18,9 @@ clean: debug: $(MAKE) lib/src/libwings.dylib CXXFLAGS="-g -DDEBUG=1" +macos: + $(MAKE) lib/src/libwings.dylib + example: debug lldb -o "target create dart" \ -o "process launch --stop-at-entry example/main.dart" \ diff --git a/example/main.dart b/example/main.dart index 204795ae..623da42c 100644 --- a/example/main.dart +++ b/example/main.dart @@ -5,7 +5,6 @@ import 'package:angel_framework/angel_framework.dart'; import 'package:angel_wings/angel_wings.dart'; main() async { - if (false) for (int i = 1; i < Platform.numberOfProcessors; i++) { var onError = new ReceivePort(); Isolate.spawn(isolateMain, i, onError: onError.sendPort); @@ -18,7 +17,7 @@ main() async { void isolateMain(int id) { var app = new Angel(); - var wings = new AngelWings(app, shared: id > 0, useZone: false); + var wings = new AngelWings(app, shared: true, useZone: false); var old = app.errorHandler; app.errorHandler = (e, req, res) { diff --git a/lib/angel_wings.dart b/lib/angel_wings.dart index 7029acd0..45ad5ce2 100644 --- a/lib/angel_wings.dart +++ b/lib/angel_wings.dart @@ -13,7 +13,7 @@ import 'package:http_parser/http_parser.dart'; import 'package:json_god/json_god.dart' as god; import 'package:mock_request/mock_request.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:tuple/tuple.dart'; import 'package:uuid/uuid.dart'; diff --git a/lib/src/bind_socket.cc b/lib/src/bind_socket.cc index fa077c90..18ce6943 100644 --- a/lib/src/bind_socket.cc +++ b/lib/src/bind_socket.cc @@ -34,12 +34,11 @@ void wings_BindSocket(Dart_NativeArguments arguments) std::string addressStringInstance(addressString); std::lock_guard lock(serverInfoVectorMutex); -#if __APPLE__ - if (false) -#else + if (shared) -#endif { + #if __APPLE__ + #else for (unsigned long i = 0; i < serverInfoVector.size(); i++) { WingsServerInfo *server_info = serverInfoVector.at(i); @@ -50,6 +49,7 @@ void wings_BindSocket(Dart_NativeArguments arguments) break; } } + #endif } if (existingIndex > -1) diff --git a/lib/src/http_listener.cc b/lib/src/http_listener.cc index 2c557d4f..4e2f6f0c 100644 --- a/lib/src/http_listener.cc +++ b/lib/src/http_listener.cc @@ -1,6 +1,6 @@ #include #include -#include +//#include #include #include "wings.h" #include "wings_thread.h" @@ -56,9 +56,9 @@ void handleMessage(Dart_Port destPortId, Dart_CObject *message) else { // This is either a send or close message. - std::cout << "NVALUES: " << message->value.as_array.length << std::endl; + //std::cout << "NVALUES: " << message->value.as_array.length << std::endl; int sockfd = (int)get_int(message->value.as_array.values[0]); - printf("FD: %d\n", sockfd); + //printf("FD: %d\n", sockfd); if (message->value.as_array.length >= 2) { @@ -69,30 +69,30 @@ void handleMessage(Dart_Port destPortId, Dart_CObject *message) if (msg->type == Dart_CObject_kExternalTypedData) { - std::cout << "ext typed data" << std::endl; - printf("Length: %ld\n", msg->value.as_external_typed_data.length); - std::cout << "ptr: " << msg->value.as_typed_data.values << std::endl; + //std::cout << "ext typed data" << std::endl; + //printf("Length: %ld\n", msg->value.as_external_typed_data.length); + //std::cout << "ptr: " << msg->value.as_typed_data.values << std::endl; write(sockfd, msg->value.as_external_typed_data.data, (size_t)msg->value.as_external_typed_data.length); } else if (msg->type == Dart_CObject_kTypedData) { - std::cout << "regular typed data" << std::endl; - printf("Length: %ld\n", msg->value.as_typed_data.length); + //std::cout << "regular typed data" << std::endl; + //printf("Length: %ld\n", msg->value.as_typed_data.length); write(sockfd, msg->value.as_typed_data.values, (size_t)msg->value.as_typed_data.length); } else { - std::cout << "unknown type: " << ((Dart_CObject_Type) msg->type) << std::endl; + //std::cout << "unknown type: " << ((Dart_CObject_Type) msg->type) << std::endl; } } else { - std::cout << "null msg!!!" << std::endl; + //std::cout << "null msg!!!" << std::endl; } } else { - printf("Close!\n"); + //printf("Close!\n"); close(sockfd); } } diff --git a/lib/src/send.cc b/lib/src/send.cc index cfcd222e..7a67d819 100644 --- a/lib/src/send.cc +++ b/lib/src/send.cc @@ -1,6 +1,3 @@ -#ifdef DEBUG -#include "dart_debug.h" -#endif #include "wings.h" void wings_CloseSocket(Dart_NativeArguments arguments) @@ -20,9 +17,6 @@ void wings_Send(Dart_NativeArguments arguments) void *dataBytes; intptr_t dataLength; -#ifdef DEBUG - HandleError(Dart_Print(sockfdHandle)); -#endif HandleError(Dart_IntegerToUint64(sockfdHandle, &sockfd)); HandleError(Dart_TypedDataAcquireData(dataHandle, &dataType, &dataBytes, &dataLength)); write((int)sockfd, dataBytes, (size_t)dataLength); diff --git a/lib/src/wings.dart b/lib/src/wings.dart index d1d90ab2..354f2361 100644 --- a/lib/src/wings.dart +++ b/lib/src/wings.dart @@ -89,12 +89,13 @@ class AngelWings { static SendPort _startHttpListener() native "StartHttpListener"; - 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(int sockfd, Uint8List data) { // _pool.withResource(() { print('Sending ${[sockfd, data]}'); @@ -113,7 +114,7 @@ class AngelWings { } //}); //_pool.withResource(() => __closeSocket(sockfd)); - } + }*/ AngelWings(this.app, {this.shared: false, this.useZone: true}) { _recv.handler = _handleMessage; @@ -164,7 +165,7 @@ class AngelWings { } void _handleMessage(x) { - print('INPUT: $x'); + //print('INPUT: $x'); if (x is String) { close(); throw new StateError(x); @@ -177,11 +178,11 @@ class AngelWings { switch (command) { case messageBegin: - print('BEGIN $sockfd'); + //print('BEGIN $sockfd'); _staging[sockfd] = new WingsRequestContext._(this, sockfd, app); break; case messageComplete: - print('$sockfd in $_staging???'); + //print('$sockfd in $_staging???'); var rq = _staging.remove(sockfd); if (rq != null) { rq._method = methodToString(x[2] as int); @@ -218,13 +219,13 @@ class AngelWings { } Future _handleRequest(WingsRequestContext req) { - print('req: $req'); + //print('req: $req'); if (req == null) return new Future.value(); var res = new WingsResponseContext._(req) ..app = app ..serializer = (app.serializer ?? god.serialize) ..encoders.addAll(app.encoders); - print('Handling fd: ${req._sockfd}'); + //print('Handling fd: ${req._sockfd}'); handle() { var path = req.path; @@ -257,7 +258,7 @@ class AngelWings { Future Function() runPipeline; - print('Pipeline: $pipeline'); + //print('Pipeline: $pipeline'); for (var handler in pipeline) { if (handler == null) break; @@ -316,7 +317,7 @@ class AngelWings { return handleAngelHttpException(e, trace, req, res); }).catchError((e, StackTrace st) { var trace = new Trace.from(st ?? StackTrace.current).terse; - _closeSocket(req); + AngelWings._closeSocket(req._sockfd); // Ideally, we won't be in a position where an absolutely fatal error occurs, // but if so, we'll need to log it. if (app.logger != null) { @@ -354,7 +355,7 @@ class AngelWings { b.writeln(); _send(req._sockfd, _coerceUint8List(b.toString().codeUnits)); - _closeSocket(req); + AngelWings._closeSocket(req._sockfd); } finally { return null; } @@ -379,9 +380,9 @@ class AngelWings { /// Sends a response. Future sendResponse(WingsRequestContext req, WingsResponseContext res, {bool ignoreFinalizers: false}) { - print('Closing: ${req._sockfd}'); + //print('Closing: ${req._sockfd}'); if (res.willCloseItself) return new Future.value(); - print('Not self-closing: ${req._sockfd}'); + //print('Not self-closing: ${req._sockfd}'); Future finalizers = ignoreFinalizers == true ? new Future.value() @@ -399,7 +400,7 @@ class AngelWings { //request.response.headers.chunkedTransferEncoding = res.chunked ?? true; // TODO: Is there a need to support this? - print('Buffer: ${res.buffer}'); + //print('Buffer: ${res.buffer}'); List outputBuffer = res.buffer.toBytes(); if (res.encoders.isNotEmpty) { @@ -437,7 +438,7 @@ class AngelWings { } } - print('Create string buffer'); + //print('Create string buffer'); var b = new StringBuffer(); b.writeln('HTTP/1.1 ${res.statusCode}'); @@ -458,20 +459,20 @@ class AngelWings { } b.writeln(); - print(b); + //print(b); var bb = new BytesBuilder(copy: false) ..add(b.toString().codeUnits) ..add(outputBuffer); var buf = _coerceUint8List(bb.takeBytes()); - print('Output: $buf'); + //print('Output: $buf'); return finalizers.then((_) { - print('A'); + //print('A'); _send(req._sockfd, buf); - print('B'); - _closeSocket(req); - print('C'); + //print('B'); + AngelWings._closeSocket(req._sockfd); + //print('C'); if (req.injections.containsKey(PoolResource)) { req.injections[PoolResource].release(); diff --git a/lib/src/wings_response.dart b/lib/src/wings_response.dart index 2cc4a258..3a571f8b 100644 --- a/lib/src/wings_response.dart +++ b/lib/src/wings_response.dart @@ -13,14 +13,14 @@ class WingsResponseContext extends ResponseContext { if (_isClosed && !_useStream) throw ResponseContext.closed(); else if (_useStream) - _wings._send(correspondingRequest._sockfd, _coerceUint8List(data)); + AngelWings._send(correspondingRequest._sockfd, _coerceUint8List(data)); else buffer.add(data); } @override Future close() { - _wings._closeSocket(correspondingRequest); + AngelWings._closeSocket(correspondingRequest._sockfd); _isClosed = true; _useStream = false; return super.close(); @@ -77,7 +77,7 @@ class WingsResponseContext extends ResponseContext { } return output.forEach(((data) => - _wings._send(correspondingRequest._sockfd, _coerceUint8List(data)))); + AngelWings._send(correspondingRequest._sockfd, _coerceUint8List(data)))); } @override @@ -147,7 +147,7 @@ class WingsResponseContext extends ResponseContext { b.writeln(); - _wings._send( + AngelWings._send( correspondingRequest._sockfd, _coerceUint8List(b.toString().codeUnits)); } } diff --git a/lib/src/worker_thread.cc b/lib/src/worker_thread.cc index b8ea5b22..029cf30a 100644 --- a/lib/src/worker_thread.cc +++ b/lib/src/worker_thread.cc @@ -1,5 +1,5 @@ //#include -#include +//#include #include #include #include "wings_thread.h" @@ -86,11 +86,11 @@ int send_string(http_parser *parser, char *str, size_t length, int code, bool as } else { - third.type = Dart_CObject_kExternalTypedData; - third.type = Dart_CObject_kExternalTypedData; - third.value.as_external_typed_data.type = Dart_TypedData_kUint8; - third.value.as_external_typed_data.length = length; - third.value.as_external_typed_data.data = (uint8_t *)str; + third.type = Dart_CObject_kTypedData; + third.type = Dart_CObject_kTypedData; + third.value.as_typed_data.type = Dart_TypedData_kUint8; + third.value.as_typed_data.length = length; + third.value.as_typed_data.values = (uint8_t *)str; } // Post the string back to Dart... @@ -118,24 +118,24 @@ int send_oncomplete(http_parser *parser, int code) Dart_CObject minor{}; Dart_CObject addr{}; sockfd.type = command.type = method.type = major.type = minor.type = Dart_CObject_kInt32; - addr.type = Dart_CObject_kExternalTypedData; + addr.type = Dart_CObject_kTypedData; sockfd.value.as_int32 = rq->sock; command.value.as_int32 = code; method.value.as_int32 = parser->method; major.value.as_int32 = parser->http_major; minor.value.as_int32 = parser->http_minor; - addr.value.as_external_typed_data.type = Dart_TypedData_kUint8; - addr.value.as_external_typed_data.length = rq->addr_len; + addr.value.as_typed_data.type = Dart_TypedData_kUint8; + addr.value.as_typed_data.length = rq->addr_len; if (rq->ipv6) { auto *v6 = (sockaddr_in6 *)&rq->addr; - addr.value.as_external_typed_data.data = (uint8_t *)v6->sin6_addr.s6_addr; + addr.value.as_typed_data.values = (uint8_t *)v6->sin6_addr.s6_addr; } else { auto *v4 = (sockaddr_in *)&rq->addr; - addr.value.as_external_typed_data.data = (uint8_t *)&v4->sin_addr.s_addr; + addr.value.as_typed_data.values = (uint8_t *)&v4->sin_addr.s_addr; } Dart_CObject *list[6]{&sockfd, &command, &method, &major, &minor, &addr}; @@ -151,7 +151,7 @@ int send_oncomplete(http_parser *parser, int code) //void handleRequest(const std::shared_ptr &rq) void handleRequest(requestInfo *rq) { - size_t len = 80 * 1024, nparsed; + size_t len = 100 * 1024, nparsed; char buf[len]; ssize_t recved; memset(buf, 0, len); @@ -163,15 +163,15 @@ void handleRequest(requestInfo *rq) http_parser_settings settings{}; settings.on_message_begin = [](http_parser *parser) { - std::cout << "mb" << std::endl; + //std::cout << "mb" << std::endl; return send_notification(parser, 0); }; settings.on_message_complete = [](http_parser *parser) { - std::cout << "mc" << std::endl; + //std::cout << "mc" << std::endl; send_oncomplete(parser, 1); delete (requestInfo *)parser->data; - std::cout << "deleted rq!" << std::endl; + //std::cout << "deleted rq!" << std::endl; return 0; }; @@ -181,23 +181,23 @@ void handleRequest(requestInfo *rq) }; settings.on_header_field = [](http_parser *parser, const char *at, size_t length) { - std::cout << "hf" << std::endl; + //std::cout << "hf" << std::endl; return send_string(parser, (char *)at, length, 3); }; settings.on_header_value = [](http_parser *parser, const char *at, size_t length) { - std::cout << "hv" << std::endl; + //std::cout << "hv" << std::endl; return send_string(parser, (char *)at, length, 4); }; settings.on_body = [](http_parser *parser, const char *at, size_t length) { - std::cout << "body" << std::endl; + //std::cout << "body" << std::endl; return send_string(parser, (char *)at, length, 5, true); }; unsigned int isUpgrade = 0; - std::cout << "start" << std::endl; + //std::cout << "start" << std::endl; while ((recved = recv(rq->sock, buf, len, 0)) > 0) { if (isUpgrade) @@ -217,6 +217,7 @@ void handleRequest(requestInfo *rq) } else if (nparsed != recved) { + //std::cout << "Hm what" << std::endl; close(rq->sock); return; } diff --git a/pubspec.yaml b/pubspec.yaml index 90b0a776..4e07f55c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ dependencies: angel_framework: ^1.0.0 build_native: ^0.0.9 mock_request: ^1.0.0 - pooled_map: ^1.0.0 + #pooled_map: ^1.0.0 uuid: ^1.0.0 dev_dependencies: build_runner: From c9492122b76413c4a2911dd7722ec6906c7a8f89 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Tue, 10 Jul 2018 11:06:29 -0400 Subject: [PATCH 05/28] Basic README --- Makefile | 2 +- README.md | 51 +++++++++++++++++++ example/main.dart | 10 +++- lib/angel_wings.dart | 2 +- lib/src/bind_socket.cc | 8 +-- lib/src/dart_debug.h | 8 +-- lib/src/http_listener.cc | 2 +- lib/src/wings.cc | 2 +- lib/src/wings.dart | 97 ++++++++++++++++++++++++++----------- lib/src/wings_response.dart | 12 ++--- lib/src/worker_thread.cc | 47 +++++++++--------- pubspec.yaml | 3 +- web/index.html | 13 +++++ web/site.css | 3 ++ 14 files changed, 191 insertions(+), 69 deletions(-) create mode 100644 README.md create mode 100644 web/index.html create mode 100644 web/site.css diff --git a/Makefile b/Makefile index 1a49f460..68bca647 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ CXX_INCLUDES=-I$(HTTP_PARSER) -I$(DART_SDK)/include .PHONY: clean debug macos all all: - printf 'Available targets:\n'\ + //printf 'Available targets:\n'\ ' * `debug` - Builds a debug library on MacOS\n'\ ' * `example` - Runs example/main.dart in LLDB on MacOS\n'\ ' * `macos` - Builds a release-mode library on MacOS\n' diff --git a/README.md b/README.md new file mode 100644 index 00000000..4a80caa3 --- /dev/null +++ b/README.md @@ -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}'); +} +``` \ No newline at end of file diff --git a/example/main.dart b/example/main.dart index 623da42c..d1223ede 100644 --- a/example/main.dart +++ b/example/main.dart @@ -2,7 +2,9 @@ import 'dart:async'; import 'dart:io'; import 'dart:isolate'; import 'package:angel_framework/angel_framework.dart'; +import 'package:angel_static/angel_static.dart'; import 'package:angel_wings/angel_wings.dart'; +import 'package:file/local.dart'; main() async { for (int i = 1; i < Platform.numberOfProcessors; i++) { @@ -19,6 +21,8 @@ void isolateMain(int id) { var app = new Angel(); var wings = new AngelWings(app, shared: true, useZone: false); + app.injectEncoders({'gzip': gzip.encoder, 'deflate': zlib.encoder}); + var old = app.errorHandler; app.errorHandler = (e, req, res) { print(e); @@ -26,7 +30,11 @@ void isolateMain(int id) { 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((_) { print( diff --git a/lib/angel_wings.dart b/lib/angel_wings.dart index 45ad5ce2..7029acd0 100644 --- a/lib/angel_wings.dart +++ b/lib/angel_wings.dart @@ -13,7 +13,7 @@ import 'package:http_parser/http_parser.dart'; import 'package:json_god/json_god.dart' as god; import 'package:mock_request/mock_request.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:tuple/tuple.dart'; import 'package:uuid/uuid.dart'; diff --git a/lib/src/bind_socket.cc b/lib/src/bind_socket.cc index 18ce6943..52355bfa 100644 --- a/lib/src/bind_socket.cc +++ b/lib/src/bind_socket.cc @@ -37,8 +37,8 @@ void wings_BindSocket(Dart_NativeArguments arguments) if (shared) { - #if __APPLE__ - #else + //#if __APPLE__ + //#else for (unsigned long i = 0; i < serverInfoVector.size(); i++) { WingsServerInfo *server_info = serverInfoVector.at(i); @@ -49,7 +49,7 @@ void wings_BindSocket(Dart_NativeArguments arguments) break; } } - #endif + //#endif } if (existingIndex > -1) @@ -117,6 +117,7 @@ void wings_BindSocket(Dart_NativeArguments arguments) return; } +/* #if __APPLE__ ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &i, sizeof(i)); @@ -126,6 +127,7 @@ void wings_BindSocket(Dart_NativeArguments arguments) return; } #endif +*/ if (addressLength > 4) { diff --git a/lib/src/dart_debug.h b/lib/src/dart_debug.h index 90789d40..a3943631 100644 --- a/lib/src/dart_debug.h +++ b/lib/src/dart_debug.h @@ -11,17 +11,17 @@ Dart_Handle ToCString(Dart_Handle obj, const char** 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; Dart_Handle result = ToCString(obj, &toString); if (Dart_IsError(result)) return result; - fprintf(stream, "%s\n", toString); + f//printf(stream, "%s\n", toString); return Dart_Null(); } -Dart_Handle Dart_Print(Dart_Handle obj) { - return Dart_PrintToFile(obj, stdout); +Dart_Handle Dart_//print(Dart_Handle obj) { + return Dart_//printToFile(obj, stdout); } \ No newline at end of file diff --git a/lib/src/http_listener.cc b/lib/src/http_listener.cc index 4e2f6f0c..1dfebf91 100644 --- a/lib/src/http_listener.cc +++ b/lib/src/http_listener.cc @@ -1,6 +1,6 @@ #include #include -//#include +#include #include #include "wings.h" #include "wings_thread.h" diff --git a/lib/src/wings.cc b/lib/src/wings.cc index f884e82b..7d0903c6 100644 --- a/lib/src/wings.cc +++ b/lib/src/wings.cc @@ -1,5 +1,5 @@ #include -//#include +#include #include #include #include "wings.h" diff --git a/lib/src/wings.dart b/lib/src/wings.dart index 354f2361..ccb94779 100644 --- a/lib/src/wings.dart +++ b/lib/src/wings.dart @@ -76,9 +76,11 @@ class AngelWings { final RawReceivePort _recv = new RawReceivePort(); final Map _sessions = {}; - final Map _staging = {}; - //final PooledMap _staging = - // new PooledMap(); + //final Map _staging = {}; + final PooledMap _staging = + new PooledMap(); + final PooledMap _live = + new PooledMap(); final Uuid _uuid = new Uuid(); InternetAddress _address; int _port; @@ -91,30 +93,42 @@ class AngelWings { //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(int sockfd, Uint8List data) { + void _send(WingsRequestContext req, Uint8List data) { + _live.update(req._sockfd, (_) { + //print('Sending ${[req._sockfd, data]}'); + __send(req._sockfd, data); + return req; + }); // _pool.withResource(() { - print('Sending ${[sockfd, data]}'); - _sendPort.send([sockfd, data]); + ////print('Sending ${[sockfd, data]}'); + //_sendPort.send([sockfd, data]); //}); //_pool.withResource(() => __send(sockfd, data)); } 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(() { - if (!req._closed) { - req._closed = true; - var sockfd = req._sockfd; - print('Sending ${[sockfd]}'); - _sendPort.send([sockfd]); - } + //if (!req._closed) { + // req._closed = true; + // var sockfd = req._sockfd; + // //print('Sending ${[sockfd]}'); + // _sendPort.send([sockfd]); + //} //}); //_pool.withResource(() => __closeSocket(sockfd)); - }*/ + } AngelWings(this.app, {this.shared: false, this.useZone: true}) { _recv.handler = _handleMessage; @@ -172,16 +186,25 @@ class AngelWings { } else if (x is List && x.length >= 2) { int sockfd = x[0], command = x[1]; - //WingsRequestContext _newRequest() => - // new WingsRequestContext._(this, sockfd, app); + WingsRequestContext _newRequest() => + new WingsRequestContext._(this, sockfd, app); //print(x); switch (command) { case messageBegin: //print('BEGIN $sockfd'); - _staging[sockfd] = new WingsRequestContext._(this, sockfd, app); + _staging.putIfAbsent(sockfd, _newRequest); + //_staging[sockfd] = new WingsRequestContext._(this, sockfd, app); break; 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???'); var rq = _staging.remove(sockfd); if (rq != null) { @@ -189,13 +212,21 @@ class AngelWings { rq._addressBytes = x[5] as Uint8List; _handleRequest(rq); } + */ break; case body: + _staging.update(sockfd, (rq) { + (rq._body ??= new StreamController()) + .add(x[2] as Uint8List); + return rq; + }, defaultValue: _newRequest); + /* var rq = _staging[sockfd]; if (rq != null) { (rq._body ??= new StreamController()) .add(x[2] as Uint8List); } + */ break; //case upgrade: // TODO: Handle WebSockets...? @@ -206,13 +237,25 @@ class AngelWings { // onUpgradedMessage(sockfd, x[2]); // break; 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; 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; 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; } } @@ -317,7 +360,7 @@ class AngelWings { return handleAngelHttpException(e, trace, req, res); }).catchError((e, StackTrace st) { 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, // but if so, we'll need to log it. if (app.logger != null) { @@ -354,8 +397,8 @@ class AngelWings { b.writeln('HTTP/1.1 500 Internal Server Error'); b.writeln(); - _send(req._sockfd, _coerceUint8List(b.toString().codeUnits)); - AngelWings._closeSocket(req._sockfd); + _send(req, _coerceUint8List(b.toString().codeUnits)); + _closeSocket(req); } finally { return null; } @@ -469,9 +512,9 @@ class AngelWings { return finalizers.then((_) { //print('A'); - _send(req._sockfd, buf); + _send(req, buf); //print('B'); - AngelWings._closeSocket(req._sockfd); + _closeSocket(req); //print('C'); if (req.injections.containsKey(PoolResource)) { diff --git a/lib/src/wings_response.dart b/lib/src/wings_response.dart index 3a571f8b..40970098 100644 --- a/lib/src/wings_response.dart +++ b/lib/src/wings_response.dart @@ -13,14 +13,14 @@ class WingsResponseContext extends ResponseContext { if (_isClosed && !_useStream) throw ResponseContext.closed(); else if (_useStream) - AngelWings._send(correspondingRequest._sockfd, _coerceUint8List(data)); + _wings._send(correspondingRequest, _coerceUint8List(data)); else buffer.add(data); } @override Future close() { - AngelWings._closeSocket(correspondingRequest._sockfd); + _wings._closeSocket(correspondingRequest); _isClosed = true; _useStream = false; return super.close(); @@ -76,8 +76,8 @@ class WingsResponseContext extends ResponseContext { } } - return output.forEach(((data) => - AngelWings._send(correspondingRequest._sockfd, _coerceUint8List(data)))); + return output.forEach( + ((data) => _wings._send(correspondingRequest, _coerceUint8List(data)))); } @override @@ -147,8 +147,8 @@ class WingsResponseContext extends ResponseContext { b.writeln(); - AngelWings._send( - correspondingRequest._sockfd, _coerceUint8List(b.toString().codeUnits)); + _wings._send( + correspondingRequest, _coerceUint8List(b.toString().codeUnits)); } } diff --git a/lib/src/worker_thread.cc b/lib/src/worker_thread.cc index 029cf30a..5b06dddf 100644 --- a/lib/src/worker_thread.cc +++ b/lib/src/worker_thread.cc @@ -1,5 +1,5 @@ -//#include -//#include +#include +#include #include #include #include "wings_thread.h" @@ -12,31 +12,32 @@ void wingsThreadMain(wings_thread_info *info) while (true) { - std::unique_lock lock(serverInfo->mutex, std::defer_lock); + //std::unique_lock lock(serverInfo->mutex, std::defer_lock); + std::lock_guard lock(serverInfo->mutex); sockaddr client_addr{}; 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); - lock.unlock(); - - if (client < 0) - { - // send_error(info->port, "Failed to accept client socket."); - return; - } - - //auto rq = std::make_shared(); - 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); + // send_error(info->port, "Failed to accept client socket."); + return; } + + //auto rq = std::make_shared(); + 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) { - // std::cout << "url" << std::endl; + // //std::cout << "url" << std::endl; return send_string(parser, (char *)at, length, 2); }; @@ -198,7 +199,7 @@ void handleRequest(requestInfo *rq) unsigned int isUpgrade = 0; //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) { diff --git a/pubspec.yaml b/pubspec.yaml index 4e07f55c..5caadf47 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,9 +3,10 @@ dependencies: angel_framework: ^1.0.0 build_native: ^0.0.9 mock_request: ^1.0.0 - #pooled_map: ^1.0.0 + pooled_map: ^1.0.0 uuid: ^1.0.0 dev_dependencies: + angel_static: ^1.3.0 build_runner: git: url: https://github.com/thosakwe/build.git diff --git a/web/index.html b/web/index.html new file mode 100644 index 00000000..c0300979 --- /dev/null +++ b/web/index.html @@ -0,0 +1,13 @@ + + + + + + + Angel Wings + + + +

Hello!!!

+ + \ No newline at end of file diff --git a/web/site.css b/web/site.css new file mode 100644 index 00000000..c2140b8b --- /dev/null +++ b/web/site.css @@ -0,0 +1,3 @@ +h1 { + color: blue; +} \ No newline at end of file From 51c605cf4c9dd1a9af0dcf94167d8e8f84f687d7 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Tue, 10 Jul 2018 11:10:33 -0400 Subject: [PATCH 06/28] Don't ignore shared libs --- .gitignore | 6 ++++-- lib/src/libwings.dylib | Bin 0 -> 92576 bytes 2 files changed, 4 insertions(+), 2 deletions(-) create mode 100755 lib/src/libwings.dylib diff --git a/.gitignore b/.gitignore index b87ee36c..f661f71f 100644 --- a/.gitignore +++ b/.gitignore @@ -21,5 +21,7 @@ doc/api/ *.js.map *.o -*.dylib -*.a \ No newline at end of file +#*.dylib +*.a +*.lib +*.obj \ No newline at end of file diff --git a/lib/src/libwings.dylib b/lib/src/libwings.dylib new file mode 100755 index 0000000000000000000000000000000000000000..2c4d248d18a2d58a5b75a0ad8b49f8290bee1858 GIT binary patch literal 92576 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�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 Mt2rU1G=nSg|G=((l>h($ literal 0 HcmV?d00001 From 841cd8b55269a049ee8b4628139d18b372f2e2e6 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Thu, 14 Mar 2019 13:24:31 -0400 Subject: [PATCH 07/28] Clear everything, start over using Driver API --- .gitignore | 2 +- example/main.dart | 42 +- lib/angel_wings.dart | 27 +- lib/src/angel_wings.cc | 42 ++ lib/src/angel_wings.dart | 3 + lib/src/bind_socket.cc | 205 --------- lib/src/dart_debug.h | 27 -- lib/src/http_listener.cc | 99 ----- lib/src/libangel_wings.build_native.yaml | 2 + lib/src/libwings.build_native.yaml | 18 - lib/src/libwings.dylib | Bin 92576 -> 0 bytes lib/src/native_socket.dart | 27 ++ lib/src/send.cc | 24 - lib/src/util.cc | 36 -- lib/src/wings.cc | 69 --- lib/src/wings.dart | 535 ----------------------- lib/src/wings.h | 45 -- lib/src/wings_driver.dart | 87 ++++ lib/src/wings_request.dart | 163 ++----- lib/src/wings_response.dart | 164 ++----- lib/src/wings_thread.h | 25 -- lib/src/worker_thread.cc | 229 ---------- pubspec.yaml | 16 +- 23 files changed, 234 insertions(+), 1653 deletions(-) create mode 100644 lib/src/angel_wings.cc create mode 100644 lib/src/angel_wings.dart delete mode 100644 lib/src/bind_socket.cc delete mode 100644 lib/src/dart_debug.h delete mode 100644 lib/src/http_listener.cc create mode 100644 lib/src/libangel_wings.build_native.yaml delete mode 100644 lib/src/libwings.build_native.yaml delete mode 100755 lib/src/libwings.dylib create mode 100644 lib/src/native_socket.dart delete mode 100644 lib/src/send.cc delete mode 100644 lib/src/util.cc delete mode 100644 lib/src/wings.cc delete mode 100644 lib/src/wings.dart delete mode 100644 lib/src/wings.h create mode 100644 lib/src/wings_driver.dart delete mode 100644 lib/src/wings_thread.h delete mode 100644 lib/src/worker_thread.cc diff --git a/.gitignore b/.gitignore index f661f71f..3d05881a 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,6 @@ doc/api/ *.o #*.dylib -*.a +#*.a *.lib *.obj \ No newline at end of file diff --git a/example/main.dart b/example/main.dart index d1223ede..9c64ef37 100644 --- a/example/main.dart +++ b/example/main.dart @@ -1,43 +1,7 @@ -import 'dart:async'; -import 'dart:io'; -import 'dart:isolate'; import 'package:angel_framework/angel_framework.dart'; -import 'package:angel_static/angel_static.dart'; import 'package:angel_wings/angel_wings.dart'; -import 'package:file/local.dart'; main() async { - for (int i = 1; i < Platform.numberOfProcessors; i++) { - var onError = new ReceivePort(); - Isolate.spawn(isolateMain, i, onError: onError.sendPort); - onError.listen((e) => Zone.current - .handleUncaughtError(e[0], new StackTrace.fromString(e[1].toString()))); - } - - isolateMain(0); -} - -void isolateMain(int id) { - var app = new Angel(); - var wings = new AngelWings(app, shared: true, useZone: false); - - app.injectEncoders({'gzip': gzip.encoder, 'deflate': zlib.encoder}); - - var old = app.errorHandler; - app.errorHandler = (e, req, res) { - print(e); - print(e.stackTrace); - return old(e, req, res); - }; - - 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((_) { - print( - 'Instance #$id listening at http://${wings.address.address}:${wings.port}'); - }); -} + var app = Angel(); + var wings = AngelWings(app); +} \ No newline at end of file diff --git a/lib/angel_wings.dart b/lib/angel_wings.dart index 7029acd0..718a257b 100644 --- a/lib/angel_wings.dart +++ b/lib/angel_wings.dart @@ -1,22 +1,5 @@ -library angel_wings; - -import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; -import 'dart:isolate'; -import 'dart:typed_data'; -import 'dart-ext:src/wings'; -import 'package:angel_framework/angel_framework.dart'; -import 'package:body_parser/body_parser.dart'; -import 'package:combinator/combinator.dart'; -import 'package:http_parser/http_parser.dart'; -import 'package:json_god/json_god.dart' as god; -import 'package:mock_request/mock_request.dart'; -import 'package:pool/pool.dart'; -import 'package:pooled_map/pooled_map.dart'; -import 'package:stack_trace/stack_trace.dart'; -import 'package:tuple/tuple.dart'; -import 'package:uuid/uuid.dart'; -part 'src/wings_request.dart'; -part 'src/wings_response.dart'; -part 'src/wings.dart'; +export 'src/angel_wings.dart'; +export 'src/native_socket.dart'; +export 'src/wings_driver.dart'; +export 'src/wings_request.dart'; +export 'src/wings_response.dart'; \ No newline at end of file diff --git a/lib/src/angel_wings.cc b/lib/src/angel_wings.cc new file mode 100644 index 00000000..3cbec736 --- /dev/null +++ b/lib/src/angel_wings.cc @@ -0,0 +1,42 @@ +#include +#include +#include +#include + +// Forward declaration of ResolveName function. +Dart_NativeFunction ResolveName(Dart_Handle name, int argc, bool* auto_setup_scope); + +// The name of the initialization function is the extension name followed +// by _Init. +DART_EXPORT Dart_Handle angel_wings_Init(Dart_Handle parent_library) { + if (Dart_IsError(parent_library)) return parent_library; + + Dart_Handle result_code = + Dart_SetNativeResolver(parent_library, ResolveName, NULL); + if (Dart_IsError(result_code)) return result_code; + + return Dart_Null(); +} + +Dart_Handle HandleError(Dart_Handle handle) { + if (Dart_IsError(handle)) Dart_PropagateError(handle); + return handle; +} + +// Native functions get their arguments in a Dart_NativeArguments structure +// and return their results with Dart_SetReturnValue. +void SayHello(Dart_NativeArguments arguments) { + std::cout << "Hello, native world!" << std::endl; + Dart_SetReturnValue(arguments, Dart_Null()); +} + +Dart_NativeFunction ResolveName(Dart_Handle name, int argc, bool* auto_setup_scope) { + // If we fail, we return NULL, and Dart throws an exception. + if (!Dart_IsString(name)) return NULL; + Dart_NativeFunction result = NULL; + const char* cname; + HandleError(Dart_StringToCString(name, &cname)); + + if (strcmp("SayHello", cname) == 0) result = SayHello; + return result; +} \ No newline at end of file diff --git a/lib/src/angel_wings.dart b/lib/src/angel_wings.dart new file mode 100644 index 00000000..ea16d06a --- /dev/null +++ b/lib/src/angel_wings.dart @@ -0,0 +1,3 @@ +import 'dart-ext:angel_wings'; + +void sayHello() native "SayHello"; \ No newline at end of file diff --git a/lib/src/bind_socket.cc b/lib/src/bind_socket.cc deleted file mode 100644 index 52355bfa..00000000 --- a/lib/src/bind_socket.cc +++ /dev/null @@ -1,205 +0,0 @@ -#include -#include -#include -#include "wings.h" - -std::vector serverInfoVector; -std::mutex serverInfoVectorMutex; - -void wings_BindSocket(Dart_NativeArguments arguments) -{ - // Uint8List address, String addressString, int port, int backlog, bool shared - Dart_Handle addressHandle = Dart_GetNativeArgument(arguments, 0); - Dart_Handle addressStringHandle = Dart_GetNativeArgument(arguments, 1); - Dart_Handle portHandle = Dart_GetNativeArgument(arguments, 2); - Dart_Handle backlogHandle = Dart_GetNativeArgument(arguments, 3); - Dart_Handle sharedHandle = Dart_GetNativeArgument(arguments, 4); - Dart_TypedData_Type addressType; - void *addressData; - intptr_t addressLength; - const char *addressString; - uint64_t port, backlog; - bool shared; - - // Read the arguments... - HandleError(Dart_TypedDataAcquireData(addressHandle, &addressType, &addressData, &addressLength)); - HandleError(Dart_TypedDataReleaseData(addressHandle)); - HandleError(Dart_StringToCString(addressStringHandle, &addressString)); - HandleError(Dart_IntegerToUint64(portHandle, &port)); - HandleError(Dart_IntegerToUint64(backlogHandle, &backlog)); - HandleError(Dart_BooleanValue(sharedHandle, &shared)); - - // See if there is already a server bound to the port. - long existingIndex = -1; - std::string addressStringInstance(addressString); - std::lock_guard lock(serverInfoVectorMutex); - - - if (shared) - { - //#if __APPLE__ - //#else - for (unsigned long i = 0; i < serverInfoVector.size(); i++) - { - WingsServerInfo *server_info = serverInfoVector.at(i); - - if (server_info->addressString == addressStringInstance && server_info->port == port) - { - existingIndex = (long)i; - break; - } - } - //#endif - } - - if (existingIndex > -1) - { - // We found an existing socket, just return a reference to it. - Dart_SetReturnValue(arguments, Dart_NewIntegerFromUint64(existingIndex)); - return; - } - else - { - // There's no existing server, so bind a new one, and add it to the serverInfoVector. -#ifndef WIN32 - int sockfd; -#else - WSADATA wsaData; - SOCKET ConnectSocket = INVALID_SOCKET; - - // Initialize Winsock - iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); - if (iResult != 0) - { - Dart_Handle errorHandle = Dart_NewList(2); - Dart_ListSetAt(errorHandle, 0, Dart_NewStringFromCString("WSAStartup failed.")); - Dart_ListSetAt(errorHandle, 1, Dart_NewInteger(iResult)); - Dart_ThrowException(errorHandle); - return 1; - } - - // TODO: Rest of Windows config: - // https://docs.microsoft.com/en-us/windows/desktop/winsock/complete-client-code -#endif - - if (addressLength == 4) - { - // IPv4 - sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - } - else - { - // IPv6 - sockfd = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); - } - - if (sockfd < 0) - { - Dart_Handle errorHandle = Dart_NewList(3); - Dart_ListSetAt(errorHandle, 0, Dart_NewStringFromCString("Failed to create socket.")); - Dart_ListSetAt(errorHandle, 1, Dart_NewStringFromCString(strerror(errno))); - Dart_ListSetAt(errorHandle, 2, Dart_NewInteger(errno)); - Dart_ThrowException(errorHandle); - return; - } - - int i = 1; - int ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)); - - if (ret < 0) - { - - Dart_Handle errorHandle = Dart_NewList(3); - Dart_ListSetAt(errorHandle, 0, Dart_NewStringFromCString("Cannot reuse address for socket.")); - Dart_ListSetAt(errorHandle, 1, Dart_NewStringFromCString(strerror(errno))); - Dart_ListSetAt(errorHandle, 2, Dart_NewInteger(errno)); - Dart_ThrowException(errorHandle); - return; - } - -/* -#if __APPLE__ - ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &i, sizeof(i)); - - if (ret < 0) - { - Dart_ThrowException(Dart_NewStringFromCString("Cannot reuse port for socket.")); - return; - } -#endif -*/ - - if (addressLength > 4) - { - struct sockaddr_in6 v6 - { - }; - memset(&v6, 0, sizeof(v6)); - v6.sin6_family = AF_INET6; - v6.sin6_port = htons((uint16_t)port); - ret = inet_pton(v6.sin6_family, addressString, &v6.sin6_addr.s6_addr); - - if (ret >= 0) - ret = bind(sockfd, (const sockaddr *)&v6, sizeof(v6)); - } - else - { - struct sockaddr_in v4 - { - }; - memset(&v4, 0, sizeof(v4)); - v4.sin_family = AF_INET; - v4.sin_port = htons((uint16_t)port); - v4.sin_addr.s_addr = inet_addr(addressString); - - if (ret >= 0) - ret = bind(sockfd, (const sockaddr *)&v4, sizeof(v4)); - //ret = inet_pton(family, host, &v4.sin_addr); - } - - /*if (ret < 1) { - Dart_ThrowException(Dart_NewStringFromCString("Cannot parse IP address.")); - return; - }*/ - - //if (bind(sock, (const sockaddr *) &serveraddr, sizeof(serveraddr)) < 0) { - if (ret < 0) - { - Dart_Handle errorHandle = Dart_NewList(3); - Dart_ListSetAt(errorHandle, 0, Dart_NewStringFromCString("Failed to bind socket.")); - Dart_ListSetAt(errorHandle, 1, Dart_NewStringFromCString(strerror(errno))); - Dart_ListSetAt(errorHandle, 2, Dart_NewInteger(errno)); - Dart_ThrowException(errorHandle); - return; - } - - if (listen(sockfd, SOMAXCONN) < 0) - { - Dart_Handle errorHandle = Dart_NewList(3); - Dart_ListSetAt(errorHandle, 0, Dart_NewStringFromCString("Failed to listen to bound socket.")); - Dart_ListSetAt(errorHandle, 1, Dart_NewStringFromCString(strerror(errno))); - Dart_ListSetAt(errorHandle, 2, Dart_NewInteger(errno)); - Dart_ThrowException(errorHandle); - return; - } - - if (listen(sockfd, (int)backlog) < 0) - { - Dart_Handle errorHandle = Dart_NewList(3); - Dart_ListSetAt(errorHandle, 0, Dart_NewStringFromCString("Failed to listen to bound socket.")); - Dart_ListSetAt(errorHandle, 1, Dart_NewStringFromCString(strerror(errno))); - Dart_ListSetAt(errorHandle, 2, Dart_NewInteger(errno)); - Dart_ThrowException(errorHandle); - return; - } - - // Now that we've bound the socket, let's add it to the list. - auto *server_info = new WingsServerInfo; - server_info->sockfd = sockfd; - server_info->port = port; - server_info->ipv6 = addressLength > 4; - server_info->addressString += addressStringInstance; - Dart_SetReturnValue(arguments, Dart_NewIntegerFromUint64(serverInfoVector.size())); - serverInfoVector.push_back(server_info); - } -} \ No newline at end of file diff --git a/lib/src/dart_debug.h b/lib/src/dart_debug.h deleted file mode 100644 index a3943631..00000000 --- a/lib/src/dart_debug.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifdef __cplusplus -#include -#else -#include -#endif -#include - -Dart_Handle ToCString(Dart_Handle obj, const char** out) { - Dart_Handle toStringMethod = Dart_NewStringFromCString("toString"); - Dart_Handle string = Dart_Invoke(obj, toStringMethod, 0, nullptr); - return Dart_StringToCString(string, out); -} - -Dart_Handle Dart_//printToFile(Dart_Handle obj, FILE* stream) { - const char *toString; - Dart_Handle result = ToCString(obj, &toString); - - if (Dart_IsError(result)) - return result; - - f//printf(stream, "%s\n", toString); - return Dart_Null(); -} - -Dart_Handle Dart_//print(Dart_Handle obj) { - return Dart_//printToFile(obj, stdout); -} \ No newline at end of file diff --git a/lib/src/http_listener.cc b/lib/src/http_listener.cc deleted file mode 100644 index 1dfebf91..00000000 --- a/lib/src/http_listener.cc +++ /dev/null @@ -1,99 +0,0 @@ -#include -#include -#include -#include -#include "wings.h" -#include "wings_thread.h" - -void handleMessage(Dart_Port destPortId, Dart_CObject *message); - -void wings_StartHttpListener(Dart_NativeArguments arguments) -{ - Dart_Port port = Dart_NewNativePort("angel_wings", handleMessage, true); - Dart_SetReturnValue(arguments, Dart_NewSendPort(port)); -} - -int64_t get_int(Dart_CObject *obj) -{ - if (obj == nullptr) - return 0; - switch (obj->type) - { - case Dart_CObject_kInt32: - return (int64_t)obj->value.as_int32; - case Dart_CObject_kInt64: - return obj->value.as_int64; - default: - return 0; - } -} - -void handleMessage(Dart_Port destPortId, Dart_CObject *message) -{ - if (message->type != Dart_CObject_kArray) { - return; - } - - // We always expect an array to be sent. - Dart_CObject_Type firstType = message->value.as_array.values[0]->type; - - // If it's a SendPort, then start a new thread that listens for incoming connections. - if (firstType == Dart_CObject_kSendPort) - { - std::lock_guard lock(serverInfoVectorMutex); - auto *threadInfo = new wings_thread_info; - threadInfo->port = message->value.as_array.values[0]->value.as_send_port.id; - threadInfo->serverInfo = serverInfoVector.at((unsigned long)get_int(message->value.as_array.values[1])); - std::thread workerThread(wingsThreadMain, threadInfo); - workerThread.detach(); - } - else if (firstType == Dart_CObject_kBool) - { - // The Dart world is trying to close this port. - Dart_Port port = message->value.as_array.values[1]->value.as_send_port.id; - //Dart_CloseNativePort(port); - } - else - { - // This is either a send or close message. - //std::cout << "NVALUES: " << message->value.as_array.length << std::endl; - int sockfd = (int)get_int(message->value.as_array.values[0]); - //printf("FD: %d\n", sockfd); - - if (message->value.as_array.length >= 2) - { - auto *msg = message->value.as_array.values[1]; - - if (msg != nullptr) - { - - if (msg->type == Dart_CObject_kExternalTypedData) - { - //std::cout << "ext typed data" << std::endl; - //printf("Length: %ld\n", msg->value.as_external_typed_data.length); - //std::cout << "ptr: " << msg->value.as_typed_data.values << std::endl; - write(sockfd, msg->value.as_external_typed_data.data, (size_t)msg->value.as_external_typed_data.length); - } - else if (msg->type == Dart_CObject_kTypedData) - { - //std::cout << "regular typed data" << std::endl; - //printf("Length: %ld\n", msg->value.as_typed_data.length); - write(sockfd, msg->value.as_typed_data.values, (size_t)msg->value.as_typed_data.length); - } - else - { - //std::cout << "unknown type: " << ((Dart_CObject_Type) msg->type) << std::endl; - } - } - else - { - //std::cout << "null msg!!!" << std::endl; - } - } - else - { - //printf("Close!\n"); - close(sockfd); - } - } -} \ No newline at end of file diff --git a/lib/src/libangel_wings.build_native.yaml b/lib/src/libangel_wings.build_native.yaml new file mode 100644 index 00000000..855e4974 --- /dev/null +++ b/lib/src/libangel_wings.build_native.yaml @@ -0,0 +1,2 @@ +sources: + - angel_wings|lib/src/angel_wings.cc \ No newline at end of file diff --git a/lib/src/libwings.build_native.yaml b/lib/src/libwings.build_native.yaml deleted file mode 100644 index 9fd5b606..00000000 --- a/lib/src/libwings.build_native.yaml +++ /dev/null @@ -1,18 +0,0 @@ -include: - - angel_wings|lib/src/wings.h - - angel_wings|lib/src/wings_thread.h -sources: - - angel_wings|lib/src/bind_socket.cc - - angel_wings|lib/src/http_listener.cc - - angel_wings|lib/src/send.cc - - angel_wings|lib/src/util.cc - - angel_wings|lib/src/wings.cc - - angel_wings|lib/src/worker_thread.cc -third_party: - http_parser: - git: https://github.com/nodejs/http-parser.git - commit: 5b76466 - include: - - . - sources: - - http_parser.c \ No newline at end of file diff --git a/lib/src/libwings.dylib b/lib/src/libwings.dylib deleted file mode 100755 index 2c4d248d18a2d58a5b75a0ad8b49f8290bee1858..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 92576 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�V<9bs>@RUxTPfo319)9sDh~gHu=6BFl2qi1!xS_XZFKMk&Di4b zBIWdb-k|vx7YBFHV=0B#Ox(9>>M{P57fGV9=fAK+t6z(0XcgM6^BZm^eK8inWhWd* z#b&;yg^WDPH$7%1p61`|3y)**C9~>Hn0*BUZz{o%qf%hvGyB_uq1Q9uDH2kZwe(P@qq=_CbiMd;sP{M3Y%(Bp11o=J)5>b$iY4Oua44=Vk%q!#dY~TmFvXr55=bn)@ZfYvscf_^`0}V(@wmbN1HYME8po z>ao;TEeCOBZp!D*UKr!JbyWiG$){tpij_6YShO+A0x^za8f;-#u;0qrn2SNoO441DfZtL8Z~vL*C7$7_c--svn*X6s+b#xD zFbaz&BXV&JEx5|%iLw|YoYOIKoylCsZ<0M)f~nxoG!@|U$nr@!T+HSyeQO``Vu4jP zn=IGge@|FoN$K~KJd-F~9TLkshOk{VT3#5$)f$mxf{3uOd*K~J7G<(Tem4IUc##vH zbXRuDoPD^p|Kcay{sSuN|H$(=xrj;eX#D0djr=EadDQagE7Tj~cPBV)+I&Se#_u;V z(7LPWZ}>e14@c$q7=07tw~nl_ZCUILVaqr-_gk+)etYio@VYi&VTo&0hr{sX z{2iN;gu0skop*B&pXl)-X*ZYE^Fc*Ea)O62Wf;G#tD?_fhQeq#e;@mD&vNO@gNo3X zTeUA2Bkat+GF~0GbZV2P`ZH7F-AMi$HhdqeInigXb%6J zdaEgB@b%tf4DBkx$4r`A3BFA->GqVz*j3X*FQ@gsJhSmt%(XX9dCIJsF7IQ;6hXor zSX|uGuc1)%DpLzq^6h=f$Sg2u0>n_FR`x9E^Z~Os++*4g3WZ$92#>8W6GBd!{+yGK z2QSGGd;sQtDZ`bRe4sh<9|*bR$)fG%#w|!`zzuOpjdw|COZMo~Rbi}_G4J3^_T(#m z^zdHNt!tXuZ{YhmBatqDe9$DDN}JBzyXJADEhgHXzxbe<<6-Qlgy||ZeL2|7QU30t zr*hQ$8qO7>h=#VOi9p>))`wxImt7eTii4rAq#Mol_X+qKw`=4%Iz2rq*D08OTbR%0 zFbm%%PZ2lMUc}`bh3*pEt)3}T{ILnKzD&Aq-GoJyc5*^@zMRc6atA5YO=O5XD}q)( z>|BXyTE46Ujcf3|7UOXxk9RdfuTBi@F?b&}ybe^!Z%lNeq~XR9g3X(teY~)`8N(2B z;BVn|9dRJT(+MK)+$t?_k;|)D{@IvCjMtOuOS5xp$>>ro^=~ z8QjOtJb0OO=27svPSno)9Kz10m;9S`=D{CN>dfEC81r;`liPtZZ*_RX?|WFdu*c$k z_=n@h*M}`BeJFA5Lk9P;4}+hTJ}iLOb(;3!HxYKK^Z(8IFbK6ASG*Chf}4QzIO@pU z)JHpPr!sHLui&|#IODHG&3=YoIt#grW7y>77dD#NX-5DL%A}m*5CqdWy*XQ&@w+wd z2U}HsjONx`0r5Ud=m{0?T{8W)FrTT1C0M3l8a;0{lqzP{HSoI5)4r|5r0U##X<~bD zV7MM~n6AX}_Uw9TENGf1s|_A3;Me?SZhq#aN_PNb7LNJNsZm{+1IVplUE3# z1Ms?*DxrsfXy^4jmr$iK@r*lb%w5zF8uEUtfh_lod%b(cy&h-W`2D@K{rLTi1H9F* zHxciL&jsF(a|G?1zQB4k)0N7T{u9DA6Sf+KjoF{B z;nCH7w%5xaa4Gm@BAnc}_3yJaLF^u-)?-at;D6c(a-*#3;c606TaP;%L(5`5< z@2=GI$bI@?(9s-xY1q63VaoiSoQ>kd5&HZrvh%Ge0HoS}WU=)g?U||VIgRbPS=&!0a+|ELBu=nLjUJCh*BVkGQGU<`9@6N=}n7$b--mG6;<*xF5 zD3Pma{E19V7J}J3@x2j~%sw;zhnNH)nm>ov^?hahE-=k`=3q`!C#}~?_rcI!?Q_W`1NyR)EeEj)^i$w&Du=dYjB0{`G}GMdtyE$L zAt^pi`|~Ba!rRZm>v~+<+KRAK3+tRT6UbA=mTiiq zomk2}ED_M@YH8dU8;1&%PiQt^+Le-M;sO=CWLxHYyvE%gl!kS~>-xQt^e)2A3k$u5 zNjLm+m&E@<%i*yG?l-1-{7707^NTb4EKKKKEIeNfuj>!W^RJWVD|6gB62xF1H2<0@ z9*KKteh5ZPS9mq%jGVKzk@nO4{nFo z^^TIr`PMN!5=G2JddDdfA;s82jJp-%QwTc`VI!`{^c|UmW8};+XY?1rjQ86=|BJsP zGhO;KKFjla4`l~JfyULCmEdtND$Y1XjZ+p7f%B z{Kq23Bh@f&{VRWCW;9mcInm=q(pOCOmDLNgh1HkA>xwF?ZG@fcurl*l4I^P9tH(CL zjfd5v8Q}hfRvV+SI`-i>d^L5wfj?o4a)(R$aVn7Kp_3uiufXfV%isu4J06PPnSc$P z$Lh9ZR%d)Dsm^ev`d{=r!K1PI36HzUAE+hM$7cq^ZwBEKK`rv{HI0tv-3`CD%ajfo zUzONW;!37oUqGcJ_^B>lH^$EeZMY{IJ#Aey`oedk(L3(N4cWG6^w)4l{6{oe1ot4^ zr@s#v+)-GNRKmRum;Zxk^oMZ&xjq`*4R`2IqtRdCr{61op6v1-iAKkAhjn7l2lqQT z|KrigxU*V^Pig-iA8`26YtiUBxbFkU47f|+ zu7}$R9QVR)g*zTNPsjT>e}C@1UGY=V=q+&f!c7{8Mj20!j%;C=zO z4ekv%rXLJ zE`}o?g8@WyO-amkuxFLjTz|nt4^a>G?BI{;9E3sV@F&x6-GX#jJ%3A&zr*C+u~m%t zGE=D2D`O%8{M=OeL zImtEyyCbK1g+}m(x{H#AoXBBBc|Z($W(+FV_21&O>)_$F$ScduN@N?MTVv4dSXp6h zC!xr;S6N}ecVfjZh!s1ANE>5-I6n4eq|5`vxC54vEy;ZRitmm?QNI!#|9Jw)0e&3e zZ7=)C>At4dVv#~$(;s4yGki@0vB+`0rYB>On|w`=#v-Tqntl|Eoa}2_7mLjDHGMr6 zneA)3B^Ei!*F;^dw80Es7K@zYYq~NP`Gl_thFCY-dA_EcSmab+(^;{|iN2F7WIAE z;nKm4Zp0H7mmmFR-lilv`h|`jUs!m666ArD{1(FQ__T=WW;{H0tQ-#+3B3X{o(POf z#S>;;r(W-Ld*unC;t^jM#}!^riDtk-iV^=sGmr*efU4bBJ$cT%(da#JufrXTF9^u* z-oifx?xlZ3qop{0p?e*!7SoMyW)q6KzdxzJuL#3({jl$`i!*radIz?SvQF_m7CemC z^X@p!P2h(QVz+@N$!$NE!^9WK&KW6=ItA4jRg0rsUeIY6a%wtys(ert`&8_&P_yBG^49g)n z01M{=&N#_obzoic?|qu}Z&~$kh5FW+BaMH(2X!L5W7cDi+yu1^6i?GjoFkF;cjz+Y z8;Sfx^StiG@6q_1Ufk07U%dE@8XxlF*J^ywi(jenXT11lH2#toU!d{-_Ts0els`Hp zevrn0=jDGF>J#*D^WuNj_)}i|1&u%L#ec2w7(Ktxc%KLViN<@q_&plm;KePCcY5&~ zHU0}Per-zrD>dHb!9Sz%$GrFgjmPkvs_~zB@S`>Upcg+#;}3iBciF-Af9b*B)$!8f#s94FSos$;-tWPGt?`(A{zBugdhnlUe1{jmCnaua z-0|QyrogXFiC?Mlx4rzI(RkF0FVOfT9o8d}Q#C%piyxhm{~(Pon4{wr8UR1|+j2hU zflJ}8hI<|Q|0DPRFAvJm4>!Tx0e3IlLvS15o`U-$-0O7vp`Z5bCp`OumVad&hhU8~ z1MUpC^WiRqyBdx(m&3ow4gV{*;r}Jt?f}2SJ@5~4$J>nkFzyoTJ!3!POT_(u6+Rzr zxD@VcxN^9g;O>CC7w#dr4REi+Jq7nixWx5q5Wg>v15H0BCM;L`V7w(Ap@Ky4!a*tR!{QX3HCm?6K z_w)Ag^*89xn6tAppH*y3pq~UgQ%EkB+qy|6iO{)9FAalSj{MJ%ujyd$&&trVa4-=4DIdn`!K8eV^9) z-uVRTyGHB#J9cYMW@wZ^1ox zy+k#PpZ0d-!uwf3Y1Kc+bl-+7=jK22RfOlPz;pVG+cQAQS~GB$?^YUjut@%IsVQ%5 z%sA-o$s!b~$nTJ;FR%$uBl1}ZKS7d_B$QvMVbE_x_-Y-}DTA_LLm0+O!@CgBk3}4M zy#|(;uEkRrp7v`bId?)TtiYxP0oMWn!UwIqP3G28tET0EQhb!S)I=ci8Q@am13rW- zt)Kl&sreM-d(f)iWWIx3-{5b>yRp7S-+0s^_I$N9scRq%u}8T^ym-0n_Y z$`#`&yVOwqE7)Mu>(b%4*?A4}GGiB+i4frcUXlG$LXRdsNwS=%L2VpSc2a3|T~ zdo0iuEFKA_y@ODvh9D^GnZAfgi_+vK#DW5+Iq#dkTXAm(FW)u?zA0qg7Wn2Em(tSF zKJ?!sC!qi8wExaRlJo4bZi~D#BD4oYvc3XSoekxTf&?Z0J8yGA#gA*XU$V&SV zMsMX+PksvXCg051Uv3d_U69~Dhj2bzv71)m`ZvN)nGcpH>I19R@jdq0bjS+)i>9V+ z3>Lo`G;5*fm~cR7XhMovC|C@23mqVzx`q;6m<3Ma{=@O;j$f)$y8ubfg%By}Nquh= zG6(@HaG*!f(FuBFh_V84MQsm{3cZY z8+_F$v4^M+EC71`!6S$%&@w${;0fQYbC3=#!>@4yFC=ezb1-dVbbF|N2=Y!7f2_*c z{iT>|aM88Y%m-)STfwQNc$X3#J2mnbWJRF^_o+~);!nnOpJF=L*93-*Uj4yReP1?i zqcY@sEGzWXq*C+EQu#(H(H-Ivc1UEYqMP_0=#FvG!Ll4fmz{)ehKp`S+|&tfVy;L2 zL08}hBUeLD!GH}8_wETrCYgawGw%`KEo_GLxfSRPIR|3Isb-0#^}HQ&CYXT-eYc7= z71=$@TZDZm`I1J?av0EBPQ~#0itDaI&3D2v-UpwK;TPe1^KruZ#=2p;{!E{UtlP^Y z2}koYec>F`jMgD_l9@M*#S&Gn2xaYv`PoUbj^Uh#Au(cII$b_I?|lir*SvJPH?h2k z83iNZXcX^iox%d-z-?R^noX_9A5(_MihpG8H z)6ys43^`_xjh*L#xX|W3<{P2cq0?~eRtuBjM9hJZ`3!oACPh70$&L*inJTwqL?xdP zjdg4kKO9OuHu?iNr^s<^G!Tp6*yzbv1jj~?#v(X2`cW)`W21Gk2#$^7heJ3vx+Mm| zu~BU-f@34DAGA^&8_}Yp5gZ%E4~KAU#BNpyj*Vd8aU(c3!urpR;MgdB9ExM3X)(xY zF=l~HMx+D!Eb=2f6yudKsH`;Z_5kmD0vD$vm6dTiz_2(Sr^jl-^)>wL59#1ZmC*4| zuWkdhrwmeUcKnF+AQIC?=v{ayG=83jX~Brxl3X^my^D}%9E~XT-4W*udNt0OGh&?K zvNZBBR2QlrIZ(e3tQ4IT%Yxx)M27iz#jAHDh2Ccgg&Pl5I7^KSUTVv7JRBpC&v=KU zTjPR<;Sz5ahD*FzH^pe?0x5DXAJntjBGkn*360E;9fFH89s9rvdzXLUtcos(^}&tM zodfWxTQ7j4zMTfg%_3ck`{(M{tT{WWVPyyQE?=p#YaOhZIEfEC!%Q&yoB<3It|81f z%%=eya%!i`CJ~A-E9pEbv}1V!zMr5tg-o|G+~a*)YZw9&M{2mkt)ZMX*tIVP?Sg@z z&80ZDu~<+$zjrg_2?=>aIr#Mpiz}Lf{zYaVvUEC^f*3b{yYsM{|5)b7%dQ|1(+=7! z?QPuA0CEyYOhO2Dvvu|^t zZ-(cceHNYU6uuc7cP|4FT_f6Ls-c1MlN3fb-`*a#_|;Al*3{ zZ}+iP`7W?d zfh`d~OQP+VH?jMxZ)R&xCD3AcvvVQCjhh;fO;{-s)EW|tlfdl z2-!O91YKN0L0@152yoI1DMSYhU^g+y1j$(?X#1QuH34!rcO7SabiCuB$#NiyJSaJt zmNNhbBR`L4zM1QRU#4U%Io-_|j%O@l#&4x${3`6pg7I_ljEk6&eV17EeO|`d@r-_E zT;ye>z6On3uXHgYs>)6@o;<)h+?oUXl!e(>J(;+^0y|)T!7FSj{fE@KNJT;D1K~Z6 zj}!Grs2R>hP?~N@ z&^}K=G4+$&W^hTsx106j$Y#eEkr%ZOpmy#!2jRobb9P|?ADNDV zowHCFgJp&=DV*(9iC4lH2BLg=R$jwxm#@Gl@)*MxqM8r2I!K3`VtyGG27#d;hVah1 zlWXQ=1kE++R)>BkQh?@{Gm))M%E@=3gXU%apd~@)UbX_xZ4^U6J6y@kW?(G{Ukkqt zKHNNK9vWcT%K!~;G3|5VC;iwF6XcUq^8_MO)f`}qEWvydv?fo17Gd|n2*}-B_;A+N zxQU{0)svuTIf^-_VE{S{P?2wD0{}W+8+Z6+BnH~&Z0YhNHE7NK1!OsBosE4eMQ0d8 z)G=Dpki?1TPe{T}WwS3BdKJ3qPr=!52hH6I>-M9bpxKcL&wMJ^3NwI@ZfC&5ZDVjh zoe+Bxp3dJu4!VUYP;XWT6Y*JW=5?MI7gL5k`ItFd|^!1#bkLQD6*j;V+ z!`9s+WCyzcZYeKOrxwd2lweQ0a^&2iNkK^ptaa|xxGN!Lq~Lp+M1G@G9g`l^q%1Af z;5@-NhEreoG$er%W6l=z=de`=>A|#Ob&ws-v82U>yi$eSPGuMBmI6LCCE4}ud==El zajjmMZrzeRJ~zW}p-qw7kRgbiLA!%}iH=6DR>A+jkVxPkS_FevPOmd_kty`#@Ggk{ zk_O80V~2+GV?508FcEU01Y(C^ufcVt<1|Lnol~*8>62FrIS|e84rV_UL6K&8V7itD zN`E`2pg<%IRB0*XB<8`Oxd}hOhU8Er&9oh0@!iUL1Vhl8TyO&O;op_n$t-8?xavvg zZm3UpKDi>;xPuc0x>&jbQ}vu@b2_t-6fk>p?N)YHu6-2-)UlHU;n6|!D#%U!pjiv! zC`46F@cp^wOXfDS^(^K$x8|DPC26^)@DM$$l4`4@+UbBfOF#w$luQ-@Wl=C)U}po? zCkhO!8f>!H(nG8JgT>i+**nk{EDk&ew3SjuU(`e&L0u?1o&mIKozGhxiI`u+;O>*g zvde;fqPHLjB!$!gX;6@INw&rhSs@zds42`b%{ZRk6j`v$f2KKnAYi(}oznw_KvrG} z4$nm;vi{2XT+V07<7MdUMa?~dIXyX#D|5Hz+TS%;k^Nmte69*!u65pb(TRE11-T}y zMP@xFhuU`99nHM^PlS2#K2+Tqw3cKdZ7noM3u-iS&9@hsn{&-)gXVYpAqWuBP4K{d zzB-S-Xou%)mnO6_L(l|Qm<3#77H6}EFDny}( z>NOctePL$hcuovaW|Sl#HnksVEOq714iB(lc3#h%on2%<^cB(M+S06C+g{EXrrlhb ze(MK7GYYR4cGzv?(EVsZpWt!jW)P_=m0@N;_91!}jsR3{SlW3Rxv_X{!KWC%hq$ll z6UvzuswF+3#mP6m=i&?rw==3Jdq2x03-0pHAQ znQuIKgOvptMbt1D?Qk71qnRWLgPNp6{Kz$kAA1er$6kc^%|MlL@mp`!-j5zz?~d}d zkTi2MW}GZyz!--Ine)Rl!^cRP{GehkdQ4)r0naG|g;b{qi*zw3;zZ))Doim!DidK@ z3dg(BFN){g&Ce?NbE2(+zwQI;Rd&2MVQIyyR{iHpjv+bi@T~;$U0Q(Ujfr=tRnH0Xiwt#!<(hLzqKcKur6%qU2|iK zebS!%zwZN|i=p&h<-_j-m#?F}NBt*(@Uijh{n-($??$L6p=3N`Yy7;lhs7}u3}Yk? zb9XudChDPTs(qE`&60a;e12UAC8O-2Ed|(RoQI+~o523Een?N!AYMZxjur;=Maq8E zAEVCXq73a2<1=R9czdvcb^9x{!!Kv-mQgLICgzCXMzy|^FR$BENj-Fm&suGpKUUy4;3J?VVdbD}3@%X6VT7s>M? zc?RWqi9B=Vd8s_}<+((jOXc}lc`lRZ7v%Xxd0s8g8D$g=`eo3CI{iG?38DhJfD!~ z26^_#bE7;r$#b(j`{lV+p1+ajQ}P^;=d<#BPM+K3IVjKnmgkG|{G&XFX zaU&EdW9T;wUCq$T3@v47H$#^&bT}Rn{Pr{IVCZay7Bh4jL)S2LJVQ4zl*tf(Ha2ny zLsVmtNen&5&^tg{#$Ox}yLj0&Xx+%GOgodI7a1yJ=vjsyW~iT`oeXVY=t!uE$RiA0 z%MhDT_DhD^7&-`pI`S=sKFQGS4Dk)E$Sn-r!cZMU4>MHG&{GVpVCb(5eSx7vF;pY@ z44uKyMGRfU(76m1F!V`=iW!>4(AOB^n>1xlFhpBx**1o-!!XMJ#t;HVu6^uDFiPZH z5k2W}_$_6Zvt6%Z`Y!u2^SsE=0}SEi!f4rnn4ly53?0eP28K>!=n;kj4E>m)r3|$( zRK(D?7^-0Cc7_@mx`m-{GE~RV4;U(E=n;liF!UrtUts82hVmKWx;SzXL&FTw08@4# z)>n~FGIRn%vl!wMFfxOo3mKZu&=Q8GGE~G6SMp_54DH0~qO6f2ZmOgAJlfD;Oyo}7 zVAsnZ17L%3EN9v+h};L3&S7Nrk%rL*U;d)^%$|5MLlZc8@Q(plrw#rjka0L3IZWkW zEbqgRKKWyShqX~A{0a{Wr~D<-RO=-&a1UG_vvWvb-(&D@yDR|^ z+x9y&zhN-Ik@gG|?nK~j4{}k_s?{Yc{52K+;;NFOni79?Me(&IHD?$X7L}J*)cC7P zYO729MJrZRl~h;zODn2k8R8|cSyjGbl;qW`s%uKhiTav~+JqcMt5;VP7u8f$oxAk> zbIx&NC9|qmeWj$Z#$P_$e{%WB{*t=ll9CnG{_BeBR$W(noxi%ava+J82Bb2dQB=ON zWOd;UtIAha8#QI1e1*TbqP)7MsqpYT;vaqtKs=B1Au%xb}xVEOmIAcXo zRZU?{MaAkfuc=+NdPQM*QO&CBOU|q*TUE7!nQPXZnSlL_1RQ53gwH58f=id?7v|?I zxgxNnFtB9FrI*dps%HDoJI^1?4-{VZ83RO>mlW5mswg)qt19Z&oSu+?Aox>r`s$MM zl{IBXO;u5Ob!kb}=_TdG6)Vs(qqcHoRndwPBg_AZPxy~7D8IJ6;)Zg685)cBRM()l zj(1^KlvUKOUQvE>jeliH4RTbKoN>HSTvl6tZOIDb+LDsW(~DN4kw)?Ais}+W$vpjh zmw-Ygz<=C%eyM3}a6xYFWtU!Dn3uE6C>Nfip>tyBN>>-HtoDDxe^KFu!GcRa6Ic|3 zq*R1bSuWwVeZ(^NkD9MXSndON=XSSXEP8hEDP4S5?$h z6j!XSHV`VtpkPm-r!SP@v%<(JE-tBroEn!_l%JkcTT@n1wF*^%`~Jn{7(dq`3~9Ku z!cXMre`85WH9F0UFQBYo_&HM@w_;X8ELfV#;6jeii8jB&4Rpm&yqNM8jk}7|os;WXoW$|?oFLo`+W$#}M z^>ahf8VFR?^^iC}q`<>#xUg9NP*eIpTT)fcUSv;S;c0W@vqe>_iXbmJtE(&gmzJ!o zfbiEqeix$$N=sK2uc9zr0i9liKFY1AsPr!?sR6rPkqOjc2(4hGq~?YS=!&sKMpSo6 zRaJQf6#jK3#w>Tfne9g+#=Sph$&#GU7hbXW3xQdZ7jp?96;*{bMc2&MnM8}PD5);4 zT2(2dIAKE2d8QB=$T$wVxVV3WuPrBZ4n2`0A-Qn*h!`-wIj=kh$AVIY#_9KmSrUo zu`R`t9jB|<(?UT|O2g2ChnA%*txMU4p&T=XbJEgjT{^U(%fu~nm<-dIYNnkGlukRp z@80i8Pgj;L+X4DVUUJ{3-(9}Dy!-C@yU%64SvrPpncL8|Y!>e<+Ntf-*Gd=Msnc^N z2Pu2hX`_Ckw`liu`l3Fu!50-9eQoU`6pTbgs4FVG!9c+0jf!9>>JJ7YA{uV<`d}Nn zSab%xo5bc#-e9NL9PIXqP&gRs@COhWKSFI$??w^vwS^(N0@Ck#at!;V=n6>D=Iw+{ zC>V}HQ=2#H@AgM87T%3M@1`JP-|P!-@QKamu%19L>hHK%M7q{TykY-(9~75}{=goJI!GPDMCnkwCo>0tNF5aU#54f-7Mi`$EJmob~A^|81WRnpcvqA5>=cf z`)c8-cQnJ$?`r1rk&B^nV1}^T%^Xfop$ci*P8-%A=x*!u zw;R4-hY^Xk;T56RKQ`c$jf*pub;bsr!=;xxi;hQDD0+)9{KI<@?&u6&C@`+XkQME* zO1!go4;nLPCXpEYK=HMA_L(i1tXQ4Fh_Bt?7Z5L>=!<1z7fW(4Vpkgnt)Do?V{DO+GPJJbS~d8FKSMaun|4=07OCneET zy4e@q7;G>1J0QFAGH+es&Y~asZB?<68XPlGJ5m@uSY2fUVY<=?r`)wIy0I8G9m9~> z8g@&UFO2p(8ukY^6n&vj#6Skmk_Ks3-yPw$4V!&|Qd+j5gQ4C{`s(a3d>4h#9%2mJ z+2M@uhz-Ti=p^1JU3nU#Y-LVbBJ1O|}NY^Nu2Z1I^b4OSZ~_Vbo%o zAXv3@19Wr&mRNZLXa*rCT-L~oq0v!9+bm^>2vK#=Ur$ru zexx!SXzS$Hi{?1PG}b^3w(-C6de<(jAxw#sFTiE27Q|x&G9^s3O3Z% zh4+Z39L{7$b%g3kVKx#7UKqfv5s|4`loHl5$T_y~(rk=>N6^xU zNEU2tcAr-)Ned*A6;_BiYP;+%7Z%k61e)!edr&01Cy>y7s%wr$^$ zymV*(z+fu9YbcW)9@#y*=VS2jQKM%D_fLpV%_%K!glDV>&FK=hx_W!VqGJ{x%fXf` zUABD1%IZ31aepK7G*_L6@fZC#1E1EzVqHhP8-~__PJ^ZC_UDT@>k1s+H1v1q1Hzmg}7>}_GHLLWbTb1l%eS<7V zSzgYP>LXoa9B%S#g|B1xTUd^|hyUxs_!f%R!c-EH7ty9m`u-jZ5(Ebn3Y0LyQ&e1hduET3gL z$?`>(2U#9s`CFE+v24IWSh|+7Y-IUvmZz{hgXMWFTUegYGQx6{6D)tu zauI#*2iIvVeJnrB@(PywSdO!Nj^%%`{3FYEE@OQxn_0H8l#w019W8;QC2+I^j+VgD z5;$4{M@!&n3A||~(0Iy;>BVVh&(@7SeH7{Wt%k8R5XrhdiosyWMZI>nZ$xa_<;b&Z zlVP}<2SEK8l`gr((jn^C!wJ)+yTXM+7~4ljjWkx!Q`jEG)~zw(68T1Qz*=Q|rxD+= z)7U;78|oAJ*1kQUE?{Y`*C1*Pmhp1q=Ax4g=RIV?Hbt_OGLOfQTQ@|nNf7@e391S< zTK$0t9D8G#WE`4k{n{TVD>p6B8sLf~vl)Nf=Y!qaBB6#Xe9eY2lt}wJlP+IpuS;T( zLS&ijt5bziL);dnA$RkIh(DvYgo(u9gLXHxyDi#l(;Py2o3q2%sq$Yy*>MdICb39w zq_aaZ$4I=Q^H8S2<)Qq!S7FgI1{#FXhmy%A{E;RD`SgW4Mndk@k^^K0N>bt?Ysk4W z|23wtGP|>=Y8ks!sbto8o58L{+CM zqx)PLdl7DJTDvPt)%UXl7IhNcJZkT3EU}f_%&#Z>ao25#<=A*WD9@aWP zw+4@1^z?9Mhe4DIBLm(Gdb#Wle|VE@5J!Shyvxo7=x;$6ygQaQzyvem$)WggKYB>8 zX~3n#mLv5a@Q_BI&6a+|8p?3`#0I;Goe0NW z7nu$l64?M4Vk3#Rq3y$RpBb(_rYo_owkL+7sV=l{EvqQxj-p$}CplQKOPB0;GZB`f zrX}qeMiSY!ECtWmOzS{mH>)T6z0=rt45bE25$CNwNGAo`XSGxrA&wF`jai*&7t?gO zx6DBfVJIeVDe2=SAH)6qr0nscO$gioZ?N}LbgkqtQF0hcrDNN%J6tT5{YAG(Je5um zqY#8aS^kqHC-VV-ZJFu^6N0oJusVkm+2Ntmqnj$Z$+SmPmXseVI!1R4rFQ#3R#t>C zC@qxSz!~go2b-fUF6nyolcIgK(nlC0n^-1c`pDJM$g*n?gPT4BDMpo{fP3rkR59c= z$8RpfP-73gTbTrnBL`B0emO4lzzriQylmF7F^xIIuKaw|(A$;TW`Lx;T@GFFOjr$u z%kKWvcJ$wJ{O$F~F$>~3Q1l>&E%O@9;^j^bCbOiHUbkISM?z;TO+3q@&O=8)r<_|L zeT8yedT;Ng$qBr@IE z7L+g8+)Fmud8|vH$Ss$=k!>c*2 zC$}kb1F20u+Cv1$1L|BVEmanzmEy&~`>PUWhvF(ZxkVI!8M&lxFI%8-BbX<+8$^3* zCc!>1M1!y!TSb%P^@y8Ss#&5REs40}IIxG%0nkLYTh83XtH%qwTU-(l-?Mqdl}lG+ zzKrtChTKi6TQ@tR``{c#QK(+aic!koyb;ztKbDIJFafPVI_)a`%4~Zw2%xhTtrzN6p&a;!3 zQCVJJRBsMEsyWQ%EAhN# zc^uI-tCx)Oqo$5yP*+;eQbz0fP_?fE$6 zVP4~+s;hhtEtzwDehu&HR2=CkPqYOgGziuu?wJo8HtZfqxRQ=@eIn6TRmIqaj? zHF0yNa~i9*m#-{!s~{R>vTA@^@i!62uA#U#D^mYxMT>pcGCmIza=VAr5gBUt#nkb3 zk=tt%b}@kuJKOEKyGTtfs|hbIf2&@xy(^JUI@vuTrzSxAYO9NQ9tQ)U1K^XZfZ2i?THGBDGCC=b*!B($kONDR*-{YDwV>bf36| z?&J4Mn+GVcJWG^ow4MpNniB9JdC7mB?zwM}M;ynI2S~o!NC$WBgP(EO!{@i@K8a7L zz$cwnuT$@Ug&SP8{(lZ58O#p{ab^mur}6+As(FdZJd_kKQ$61=qmcK)cj@=U_YqM& z&x8Xw4i$2H)EH)?JdK$Q^@^*GSHI;nTXPrJ9xr1?LE@2=#~F$bBzwhg5bwl4%1qIh zSXHK?@}TbKSaNW>b$jtMr8EU+h?J^wTL`xkVd`V~d9nYMYv zRVRpRPe9S$ZHGhdLAzX}!ENkJXWo@|Ih}=ihtrX(b2%L@XUH+N$k95s+?lI$PBgfj zIs1gI)zLH2kh8h+Z=YQ3ayXr>4%~1}+Qwe6<(H1z3bs(;gbAA~op$9HPvN8PE=OT; zXv{WQm$OaOwT|1z8m8(B_CkXU=5!(&SF8L-Y1#NR__lR+apHG{a3P6nehA*{BD+}k zb0IzfJo?YX+qH}DzXTpGQ5O=bBn(>p2^Ne3(JkI$1 z<5(Z_GrJj2F;1J4fqOcbKiRGD zM>K4EhYD}HZ(;0W{^}#9JL5CKb*A_VjK9p-agox0{yUYsi}69mA;ul=Qu&+sj`H7a zV$=t^wlmIy6HV7A8BhLB;U^gv7{AWg@q6Wd{z*!|i}7WQLyVtcoMwE?yScm>Z(uxb zhG#s*_-V%Ce^h+0GY&ES(0i1=9OF+io?!em;{xM9Gj<(T`n>pj2G#c-#+NZ3WBe@R zNyhaj!$017;PbcF%Ri*OjdLZ`JU_8vdce z2%@fon)}IkR#tt6hWBXrCJi6d@Y(nVj#b|-4d1BYZ)*6*8m5n5TlF=rEXVKHaI1zd z(eN$}U#{VO8veS5AJy=;HT;ai#qxe$bN`Lz{s+x{xuZOPr)t=xa8dtS%{{2Oe?-G6 z4PUO|&uTcY;V)|VP7Ob(VfxzyNNh2^6B^#H;Ykf2(D0Oo3mR@{ERS!ghToy#lQsNT z8g^@Vt%h4QJcQRQ1Cxarh8cm`4KoU}2j*iiABXt_%qL+!1@mc`9L!}fm&05Eb0y4G zFjvD|1G5+AGcccp*$fka3BrV6w!mBq6NZVvd=BP%m>Xd7FgL>71oL^AE|_kZ3t?yo zOWnp+m`h+j4D%6~n_<2H^F^3_Fdv06V0vI~h4~W9mtk&)xdUbl=5Jx{g!u~0S7Gjg zxd-N6nEPPvhj{?zL71<@d;?}2=9@4N!F&tmVVFl?9))=f=5d(6gLwkx?_r*V`8Lc` zFt}Q;!z~JP3n3KY-{5u|%-t|wgE{&v-uP$X6xh^u*RnlA{@gRHOGx*b*EOVUWm+g{>ilZ*_w+TTW|jZ)VXmFSbCb_~zD()k1-tx9$ayH0B+rB#{_$r>T7FPT8q_ zJ?o~J)n4wIDGT%CVWr(S)4sR|r!08$(A*U)vy{7lWj4~c+-}pXY45Gfs(e?m%&EA+ zSY~T(Gb+nW#U3Numo^%~bJEyG6*g+uN`1}NkQ}9H*u=`Jl1HrUbp|opGI!WSOubdG z#1~fBLKN05EDYhIlcWY0k%bdnw2PQz;F3)R3z(gV{;PgkdB1E^PWV-pTj-$&imB;6l!x3Wb zE?AkBuDeiWl7wneGA%)O26sJt6-sVN!%SSeTrQW6@_@PBst&sMa);GyjC1H#WUfl(%i-=X65Jv9V8fu;7|?v z6?aRP9&(_9fhf7`^Nz{`rg$kZn6}oV3^ksmY*$L~yl^T3#O?lmqev;QlIHSgtCBa5 z*dhW>Ls;`UQ-rln4Cbd2M}v!pc5BrTnqjc*_4wNdM|?gyzGZoi`dO<=4cf?Cor*6yzNG(Xvj|&7)kIGi=Qi?j~U>7>!Uxr z;J=`A0H0kLz{e2=JoJr%1$^XSmP6TVD+}c>FoD%d5T_lt`Khd^9?tJ05USG1Ph6-* zusG|-QLEUHb)K)j>1&zCOAGd9Y%Ol~OuLce3v6mS&2Ys>LLJF+MygMB?X?7VFD$rc zn&5OIat|SWh?)k>- zsTQ+56pT1X039f=gIT1X0-k38ZKGPfLZ3OW+S;?M&eBzZm#~OkmLCOacCIP(Ll9ax1C6^G{PyI)uTE Mt2rU1G=nSg|G=((l>h($ diff --git a/lib/src/native_socket.dart b/lib/src/native_socket.dart new file mode 100644 index 00000000..c2bbe8e7 --- /dev/null +++ b/lib/src/native_socket.dart @@ -0,0 +1,27 @@ +import 'dart:async'; +import 'dart:isolate'; +import 'dart:typed_data'; +import 'dart-ext:angel_wings'; + +int bindNativeServerSocket(String addr, int port, SendPort sendPort) + native 'Dart_NativeSocket_bind'; + +void writeToNativeSocket(int fd, Uint8List data) + native 'Dart_NativeSocket_write'; + +void closeNativeSocket(int fd) native 'Dart_NativeSocket_close'; + +class NativeSocket extends Stream { + final StreamController _ctrl = StreamController(); + final int _pointer; + bool _open = true; + + NativeSocket._(this._pointer); + + @override + StreamSubscription listen(void Function(int event) onData, + {Function onError, void Function() onDone, bool cancelOnError}) { + return _ctrl.stream + .listen(onData, onError: onError, cancelOnError: cancelOnError); + } +} diff --git a/lib/src/send.cc b/lib/src/send.cc deleted file mode 100644 index 7a67d819..00000000 --- a/lib/src/send.cc +++ /dev/null @@ -1,24 +0,0 @@ -#include "wings.h" - -void wings_CloseSocket(Dart_NativeArguments arguments) -{ - Dart_Handle sockfdHandle = Dart_GetNativeArgument(arguments, 0); - uint64_t sockfd; - HandleError(Dart_IntegerToUint64(sockfdHandle, &sockfd)); - close((int)sockfd); -} - -void wings_Send(Dart_NativeArguments arguments) -{ - Dart_Handle sockfdHandle = Dart_GetNativeArgument(arguments, 0); - Dart_Handle dataHandle = Dart_GetNativeArgument(arguments, 1); - uint64_t sockfd; - Dart_TypedData_Type dataType; - void *dataBytes; - intptr_t dataLength; - - HandleError(Dart_IntegerToUint64(sockfdHandle, &sockfd)); - HandleError(Dart_TypedDataAcquireData(dataHandle, &dataType, &dataBytes, &dataLength)); - write((int)sockfd, dataBytes, (size_t)dataLength); - HandleError(Dart_TypedDataReleaseData(dataHandle)); -} \ No newline at end of file diff --git a/lib/src/util.cc b/lib/src/util.cc deleted file mode 100644 index be5017d5..00000000 --- a/lib/src/util.cc +++ /dev/null @@ -1,36 +0,0 @@ -#include -#include "wings.h" - -void wings_AddressToString(Dart_NativeArguments arguments) { - char *address; - void *data; - intptr_t length; - bool ipv6; - Dart_TypedData_Type type; - - Dart_Handle address_handle = Dart_GetNativeArgument(arguments, 0); - Dart_Handle ipv6_handle = Dart_GetNativeArgument(arguments, 1); - HandleError(Dart_BooleanValue(ipv6_handle, &ipv6)); - sa_family_t family; - - if (ipv6) { - family = AF_INET6; - address = (char *) Dart_ScopeAllocate(INET6_ADDRSTRLEN); - } else { - family = AF_INET; - address = (char *) Dart_ScopeAllocate(INET_ADDRSTRLEN); - } - - HandleError(Dart_TypedDataAcquireData(address_handle, &type, &data, &length)); - auto *ptr = inet_ntop(family, data, address, INET_ADDRSTRLEN); - HandleError(Dart_TypedDataReleaseData(address_handle)); - - if (ptr == nullptr) { - if (ipv6) - Dart_ThrowException(Dart_NewStringFromCString("Invalid IPV6 address.")); - else - Dart_ThrowException(Dart_NewStringFromCString("Invalid IPV4 address.")); - } else { - Dart_SetReturnValue(arguments, Dart_NewStringFromCString(address)); - } -} \ No newline at end of file diff --git a/lib/src/wings.cc b/lib/src/wings.cc deleted file mode 100644 index 7d0903c6..00000000 --- a/lib/src/wings.cc +++ /dev/null @@ -1,69 +0,0 @@ -#include -#include -#include -#include -#include "wings.h" - -// Forward declaration of ResolveName function. -Dart_NativeFunction ResolveName(Dart_Handle name, int argc, bool *auto_setup_scope); - -// The name of the initialization function is the extension name followed -// by _Init. -DART_EXPORT Dart_Handle wings_Init(Dart_Handle parent_library) -{ - if (Dart_IsError(parent_library)) - return parent_library; - - Dart_Handle result_code = - Dart_SetNativeResolver(parent_library, ResolveName, NULL); - if (Dart_IsError(result_code)) - return result_code; - - return Dart_Null(); -} - -Dart_Handle HandleError(Dart_Handle handle) -{ - if (Dart_IsError(handle)) - { -#ifdef DEBUG - Dart_DumpNativeStackTrace(NULL); -#endif - Dart_PropagateError(handle); - } - - return handle; -} - -Dart_NativeFunction ResolveName(Dart_Handle name, int argc, bool *auto_setup_scope) -{ - // If we fail, we return NULL, and Dart throws an exception. - if (!Dart_IsString(name)) - return NULL; - Dart_NativeFunction result = NULL; - const char *cname; - HandleError(Dart_StringToCString(name, &cname)); - - if (strcmp(cname, "AddressToString") == 0) - { - result = wings_AddressToString; - } - else if (strcmp(cname, "BindSocket") == 0) - { - result = wings_BindSocket; - } - else if (strcmp(cname, "CloseSocket") == 0) - { - result = wings_CloseSocket; - } - else if (strcmp(cname, "Send") == 0) - { - result = wings_Send; - } - else if (strcmp(cname, "StartHttpListener") == 0) - { - result = wings_StartHttpListener; - } - - return result; -} \ No newline at end of file diff --git a/lib/src/wings.dart b/lib/src/wings.dart deleted file mode 100644 index ccb94779..00000000 --- a/lib/src/wings.dart +++ /dev/null @@ -1,535 +0,0 @@ -part of angel_wings; - -class AngelWings { - static const int messageBegin = 0, - messageComplete = 1, - url = 2, - headerField = 3, - headerValue = 4, - body = 5, - upgrade = 6, - upgradedMessage = 7; - - static const int DELETE = 0, - GET = 1, - HEAD = 2, - POST = 3, - PUT = 4, - CONNECT = 5, - OPTIONS = 6, - TRACE = 7, - COPY = 8, - LOCK = 9, - MKCOL = 10, - MOVE = 11, - PROPFIND = 12, - PROPPATCH = 13, - SEARCH = 14, - UNLOCK = 15, - BIND = 16, - REBIND = 17, - UNBIND = 18, - ACL = 19, - REPORT = 20, - MKACTIVITY = 21, - CHECKOUT = 22, - MERGE = 23, - MSEARCH = 24, - NOTIFY = 25, - SUBSCRIBE = 26, - UNSUBSCRIBE = 27, - PATCH = 28, - PURGE = 29, - MKCALENDAR = 30, - LINK = 31, - UNLINK = 32, - SOURCE = 33; - - static String methodToString(int method) { - switch (method) { - case DELETE: - return 'DELETE'; - case GET: - return 'GET'; - case HEAD: - return 'HEAD'; - case POST: - return 'POST'; - case PUT: - return 'PUT'; - case CONNECT: - return 'CONNECT'; - case OPTIONS: - return 'OPTIONS'; - case PATCH: - return 'PATCH'; - case PURGE: - return 'PURGE'; - default: - throw new ArgumentError('Unknown method $method.'); - } - } - - final Angel app; - final bool shared; - final bool useZone; - - final RawReceivePort _recv = new RawReceivePort(); - final Map _sessions = {}; - //final Map _staging = {}; - final PooledMap _staging = - new PooledMap(); - final PooledMap _live = - new PooledMap(); - final Uuid _uuid = new Uuid(); - InternetAddress _address; - int _port; - SendPort _sendPort; - - static int _bindSocket(Uint8List address, String addressString, int port, - int backlog, bool shared) native "BindSocket"; - - static SendPort _startHttpListener() native "StartHttpListener"; - - //final Pool _pool = new Pool(1); - - static void __send(int sockfd, Uint8List data) native "Send"; - - static void __closeSocket(int sockfd) native "CloseSocket"; - - void _send(WingsRequestContext req, Uint8List data) { - _live.update(req._sockfd, (_) { - //print('Sending ${[req._sockfd, data]}'); - __send(req._sockfd, data); - return req; - }); - // _pool.withResource(() { - ////print('Sending ${[sockfd, data]}'); - //_sendPort.send([sockfd, data]); - //}); - //_pool.withResource(() => __send(sockfd, data)); - } - - 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(() { - //if (!req._closed) { - // req._closed = true; - // var sockfd = req._sockfd; - // //print('Sending ${[sockfd]}'); - // _sendPort.send([sockfd]); - //} - //}); - //_pool.withResource(() => __closeSocket(sockfd)); - } - - AngelWings(this.app, {this.shared: false, this.useZone: true}) { - _recv.handler = _handleMessage; - } - - InternetAddress get address => _address; - - int get port => _port; - - Future startServer([host, int port, int backlog = 10]) { - Future _addr = host is InternetAddress - ? host - : InternetAddress.lookup(host?.toString() ?? '127.0.0.1').then((list) => - list.isNotEmpty - ? list.first - : throw new StateError('IP lookup failed.')); - - return _addr.then((address) { - try { - var serverInfoIndex = _bindSocket( - new Uint8List.fromList(address.rawAddress), - address.address, - port ?? 0, - backlog, - shared); - _sendPort = _startHttpListener(); - _sendPort.send([_recv.sendPort, serverInfoIndex]); - _address = address; - _port = port; - } on List catch (osError) { - if (osError.length == 3) { - throw new SocketException(osError[0] as String, - osError: new OSError(osError[1] as String, osError[2] as int)); - } else { - throw new SocketException('Could not start Wings server.', - osError: new OSError(osError[0] as String, osError[1] as int)); - } - } on String catch (message) { - throw new SocketException(message); - } - }); - } - - Future close() { - _sendPort.send([true, _sendPort]); - _recv.close(); - return new Future.value(); - } - - void _handleMessage(x) { - //print('INPUT: $x'); - if (x is String) { - close(); - throw new StateError(x); - } else if (x is List && x.length >= 2) { - int sockfd = x[0], command = x[1]; - - WingsRequestContext _newRequest() => - new WingsRequestContext._(this, sockfd, app); - //print(x); - - switch (command) { - case messageBegin: - //print('BEGIN $sockfd'); - _staging.putIfAbsent(sockfd, _newRequest); - //_staging[sockfd] = new WingsRequestContext._(this, sockfd, app); - break; - 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???'); - var rq = _staging.remove(sockfd); - if (rq != null) { - rq._method = methodToString(x[2] as int); - rq._addressBytes = x[5] as Uint8List; - _handleRequest(rq); - } - */ - break; - case body: - _staging.update(sockfd, (rq) { - (rq._body ??= new StreamController()) - .add(x[2] as Uint8List); - return rq; - }, defaultValue: _newRequest); - /* - var rq = _staging[sockfd]; - if (rq != null) { - (rq._body ??= new StreamController()) - .add(x[2] as Uint8List); - } - */ - break; - //case upgrade: - // TODO: Handle WebSockets...? - // if (onUpgrade != null) onUpgrade(sockfd); - // break; - //case upgradedMessage: - // TODO: Handle upgrade - // onUpgradedMessage(sockfd, x[2]); - // break; - case url: - _staging.update(sockfd, (rq) { - rq?.__url = x[2] as String; - return rq; - }, defaultValue: _newRequest); - //_staging[sockfd]?.__url = x[2] as String; - break; - case headerField: - _staging.update(sockfd, (rq) { - rq?._headerField = x[2] as String; - return rq; - }, defaultValue: _newRequest); - //_staging[sockfd]?._headerField = x[2] as String; - break; - case headerValue: - _staging.update(sockfd, (rq) { - rq?._headerValue = x[2] as String; - return rq; - }, defaultValue: _newRequest); - //_staging[sockfd]?._headerValue = x[2] as String; - break; - } - } - } - - Future _handleRequest(WingsRequestContext req) { - //print('req: $req'); - if (req == null) return new Future.value(); - var res = new WingsResponseContext._(req) - ..app = app - ..serializer = (app.serializer ?? god.serialize) - ..encoders.addAll(app.encoders); - //print('Handling fd: ${req._sockfd}'); - - handle() { - var path = req.path; - if (path == '/') path = ''; - - Tuple3>> resolveTuple() { - Router r = app.optimizedRouter; - var resolved = - r.resolveAbsolute(path, method: req.method, strip: false); - - return new Tuple3( - new MiddlewarePipeline(resolved).handlers, - resolved.fold({}, (out, r) => out..addAll(r.allParams)), - resolved.isEmpty ? null : resolved.first.parseResult, - ); - } - - var cacheKey = req.method + path; - var tuple = app.isProduction - ? app.handlerCache.putIfAbsent(cacheKey, resolveTuple) - : resolveTuple(); - - req.params.addAll(tuple.item2); - req.inject(ParseResult, tuple.item3); - - if (!app.isProduction && app.logger != null) - req.inject(Stopwatch, new Stopwatch()..start()); - - var pipeline = tuple.item1; - - Future Function() runPipeline; - - //print('Pipeline: $pipeline'); - for (var handler in pipeline) { - if (handler == null) break; - - if (runPipeline == null) - runPipeline = () => app.executeHandler(handler, req, res); - else { - var current = runPipeline; - runPipeline = () => current().then((result) => !result - ? new Future.value(result) - : app.executeHandler(handler, req, res)); - } - } - - return runPipeline == null - ? sendResponse(req, res) - : runPipeline().then((_) => sendResponse(req, res)); - } - - if (useZone == false) { - return handle().catchError((e, StackTrace st) { - if (e is FormatException) - throw new AngelHttpException.badRequest(message: e.message) - ..stackTrace = st; - throw new AngelHttpException(e, stackTrace: st, statusCode: 500); - }, test: (e) => e is! AngelHttpException).catchError( - (AngelHttpException e, StackTrace st) { - return handleAngelHttpException(e, e.stackTrace ?? st, req, res); - }).whenComplete(() => res.dispose()); - } else { - var zoneSpec = new ZoneSpecification( - print: (self, parent, zone, line) { - if (app.logger != null) - app.logger.info(line); - else - parent.print(zone, line); - }, - handleUncaughtError: (self, parent, zone, error, stackTrace) { - var trace = new Trace.from(stackTrace ?? StackTrace.current).terse; - - return new Future(() { - AngelHttpException e; - - if (error is FormatException) { - e = new AngelHttpException.badRequest(message: error.message); - } else if (error is AngelHttpException) { - e = error; - } else { - e = new AngelHttpException(error, - stackTrace: stackTrace, message: error?.toString()); - } - - if (app.logger != null) { - app.logger.severe(e.message ?? e.toString(), error, trace); - } - - return handleAngelHttpException(e, trace, req, res); - }).catchError((e, StackTrace st) { - var trace = new Trace.from(st ?? StackTrace.current).terse; - _closeSocket(req); - // Ideally, we won't be in a position where an absolutely fatal error occurs, - // but if so, we'll need to log it. - if (app.logger != null) { - app.logger.severe( - 'Fatal error occurred when processing ${req.uri}.', e, trace); - } else { - stderr - ..writeln('Fatal error occurred when processing ' - '${req.uri}:') - ..writeln(e) - ..writeln(trace); - } - }); - }, - ); - - var zone = Zone.current.fork(specification: zoneSpec); - req.inject(Zone, zone); - req.inject(ZoneSpecification, zoneSpec); - return zone.run(handle).whenComplete(() { - res.dispose(); - }); - } - } - - /// Handles an [AngelHttpException]. - Future handleAngelHttpException(AngelHttpException e, StackTrace st, - WingsRequestContext req, WingsResponseContext res, - {bool ignoreFinalizers: false}) { - if (req == null || res == null) { - try { - app.logger?.severe(e, st); - var b = new StringBuffer(); - b.writeln('HTTP/1.1 500 Internal Server Error'); - b.writeln(); - - _send(req, _coerceUint8List(b.toString().codeUnits)); - _closeSocket(req); - } finally { - return null; - } - } - - Future handleError; - - if (!res.isOpen) - handleError = new Future.value(); - else { - res.statusCode = e.statusCode; - handleError = - new Future.sync(() => app.errorHandler(e, req, res)).then((result) { - return app.executeHandler(result, req, res).then((_) => res.end()); - }); - } - - return handleError.then((_) => - sendResponse(req, res, ignoreFinalizers: ignoreFinalizers == true)); - } - - /// Sends a response. - Future sendResponse(WingsRequestContext req, WingsResponseContext res, - {bool ignoreFinalizers: false}) { - //print('Closing: ${req._sockfd}'); - if (res.willCloseItself) return new Future.value(); - //print('Not self-closing: ${req._sockfd}'); - - Future finalizers = ignoreFinalizers == true - ? new Future.value() - : app.responseFinalizers.fold( - new Future.value(), (out, f) => out.then((_) => f(req, res))); - - if (res.isOpen) res.end(); - - var headers = {}; - headers.addAll(res.headers); - - headers['content-length'] = res.buffer.length.toString(); - - // Ignore chunked transfer encoding - //request.response.headers.chunkedTransferEncoding = res.chunked ?? true; - // TODO: Is there a need to support this? - - //print('Buffer: ${res.buffer}'); - List outputBuffer = res.buffer.toBytes(); - - if (res.encoders.isNotEmpty) { - var allowedEncodings = req.headers - .value('accept-encoding') - ?.split(',') - ?.map((s) => s.trim()) - ?.where((s) => s.isNotEmpty) - ?.map((str) { - // Ignore quality specifications in accept-encoding - // ex. gzip;q=0.8 - if (!str.contains(';')) return str; - return str.split(';')[0]; - }); - - if (allowedEncodings != null) { - for (var encodingName in allowedEncodings) { - Converter, List> encoder; - String key = encodingName; - - if (res.encoders.containsKey(encodingName)) - encoder = res.encoders[encodingName]; - else if (encodingName == '*') { - encoder = res.encoders[key = res.encoders.keys.first]; - } - - if (encoder != null) { - headers['content-encoding'] = key; - outputBuffer = res.encoders[key].convert(outputBuffer); - - headers['content-length'] = outputBuffer.length.toString(); - break; - } - } - } - } - - //print('Create string buffer'); - var b = new StringBuffer(); - b.writeln('HTTP/1.1 ${res.statusCode}'); - - res.headers.forEach((k, v) { - b.writeln('$k: $v'); - }); - - // Persist session ID - if (res.correspondingRequest._session != null) { - res.cookies - .add(new Cookie('DARTSESSID', res.correspondingRequest._session.id)); - } - - // Send all cookies - for (var cookie in res.cookies) { - var value = cookie.toString(); - b.writeln('set-cookie: $value'); - } - - b.writeln(); - //print(b); - - var bb = new BytesBuilder(copy: false) - ..add(b.toString().codeUnits) - ..add(outputBuffer); - var buf = _coerceUint8List(bb.takeBytes()); - //print('Output: $buf'); - - return finalizers.then((_) { - //print('A'); - _send(req, buf); - //print('B'); - _closeSocket(req); - //print('C'); - - if (req.injections.containsKey(PoolResource)) { - req.injections[PoolResource].release(); - } - - if (!app.isProduction && app.logger != null) { - var sw = req.grab(Stopwatch); - - if (sw.isRunning) { - sw?.stop(); - app.logger.info("${res.statusCode} ${req.method} ${req.uri} (${sw - ?.elapsedMilliseconds ?? 'unknown'} ms)"); - } - } - }); - } -} diff --git a/lib/src/wings.h b/lib/src/wings.h deleted file mode 100644 index da4f06c8..00000000 --- a/lib/src/wings.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef ANGEL_WINGS_H -#define ANGEL_WINGS_H -#ifndef WIN32 -#include -#include -#include -#include -#else -#include -#include -#include -#include -// Need to link with Ws2_32.lib, Mswsock.lib, and Advapi32.lib -#pragma comment(lib, "Ws2_32.lib") -#pragma comment(lib, "Mswsock.lib") -#pragma comment(lib, "AdvApi32.lib") -#endif -#include -#include -#include -#include -#include - -class WingsServerInfo -{ -public: - std::mutex mutex; - std::string addressString; - uint64_t port; - int sockfd; - bool ipv6; -}; - -extern std::mutex serverInfoVectorMutex; -extern std::vector serverInfoVector; - -Dart_Handle HandleError(Dart_Handle handle); - -void wings_AddressToString(Dart_NativeArguments arguments); -void wings_BindSocket(Dart_NativeArguments arguments); -void wings_CloseSocket(Dart_NativeArguments arguments); -void wings_Send(Dart_NativeArguments arguments); -void wings_StartHttpListener(Dart_NativeArguments arguments); - -#endif \ No newline at end of file diff --git a/lib/src/wings_driver.dart b/lib/src/wings_driver.dart new file mode 100644 index 00000000..41bba0af --- /dev/null +++ b/lib/src/wings_driver.dart @@ -0,0 +1,87 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io' show Cookie; +import 'dart:typed_data'; +import 'package:angel_framework/angel_framework.dart'; +import 'package:angel_framework/http.dart'; +import 'native_socket.dart'; +import 'wings_request.dart'; +import 'wings_response.dart'; + +class AngelWings extends Driver { + factory AngelWings(Angel app) { + return AngelWings._(app, null); + } + + AngelWings._( + Angel app, Future Function(dynamic, int) serverGenerator) + : super(app, serverGenerator); + + @override + void addCookies(int response, Iterable cookies) { + for (var cookie in cookies) { + setHeader(response, 'set-cookie', cookie.toString()); + } + } + + @override + Future closeResponse(int response) { + closeNativeSocket(response); + return Future.value(); + } + + @override + Future createRequestContext(int request, int response) { + // TODO: implement createRequestContext + return null; + } + + @override + Future createResponseContext(int request, int response, + [WingsRequestContext correspondingRequest]) { + // TODO: implement createResponseContext + return null; + } + + @override + Stream createResponseStreamFromRawRequest(int request) { + return Stream.fromIterable([request]); + } + + @override + void setChunkedEncoding(int response, bool value) { + // TODO: implement setChunkedEncoding + } + + @override + void setContentLength(int response, int length) { + writeStringToResponse(response, 'content-length: $length\r\n'); + } + + @override + void setHeader(int response, String key, String value) { + writeStringToResponse(response, '$key: $value\r\n'); + } + + @override + void setStatusCode(int response, int value) { + // HTTP-Version SP Status-Code SP Reason-Phrase CRLF + writeStringToResponse(response, 'HTTP/1.1 $value\r\n'); + } + + @override + // TODO: implement uri + Uri get uri => null; + + @override + void writeStringToResponse(int response, String value) { + writeToResponse(response, utf8.encode(value)); + } + + @override + void writeToResponse(int response, List data) { + var buf = data is Uint8List ? data : Uint8List.fromList(data); + writeToNativeSocket(response, buf); + } +} diff --git a/lib/src/wings_request.dart b/lib/src/wings_request.dart index dc56400b..e890bc40 100644 --- a/lib/src/wings_request.dart +++ b/lib/src/wings_request.dart @@ -1,158 +1,53 @@ -part of angel_wings; +import 'dart:io'; +import 'package:angel_container/src/container.dart'; +import 'package:angel_framework/angel_framework.dart'; -class WingsRequestContext extends RequestContext { - final AngelWings _wings; - final int _sockfd; - bool _closed = false; +class WingsRequestContext extends RequestContext { + @override + // TODO: implement body + Stream> get body => null; @override - Angel app; - - WingsRequestContext._(this._wings, this._sockfd, Angel app) : this.app = app; - - static final RegExp _straySlashes = new RegExp(r'(^/+)|(/+$)'); - - final Map _headers = {}; - - String __contentTypeString; - //String __path; - String __url; - - Uint8List _addressBytes; - StreamController _body; - ContentType _contentType; - List _cookies; - String _headerField, _hostname, _originalMethod, _method, _path; - HttpHeaders _httpHeaders; - InternetAddress _remoteAddress; - HttpSession _session; - Uri _uri; - - static String _addressToString(Uint8List bytes, bool ipV6) - native "AddressToString"; - - String get _contentTypeString => - __contentTypeString ??= _headers['content-type']?.toString(); - - void set _headerValue(String value) { - if (_headerField != null) { - _headers[_headerField.toLowerCase()] = value; - _headerField = null; - } - } + // TODO: implement container + Container get container => null; @override - ContentType get contentType => _contentType ??= (_contentTypeString == null - ? ContentType.binary - : ContentType.parse(_contentTypeString)); + // TODO: implement cookies + List get cookies => null; @override - List get cookies { - if (_cookies != null) { - return _cookies; - } - - var cookies = []; - - return _cookies = new List.unmodifiable(cookies); - } + // TODO: implement headers + HttpHeaders get headers => null; @override - HttpHeaders get headers => _httpHeaders ??= new _WingsIncomingHeaders(this); + // TODO: implement hostname + String get hostname => null; @override - String get hostname => _hostname ??= - (_headers['host'] ?? '${_wings.address.address}:${_wings.port}'); + // TODO: implement method + String get method => null; @override - HttpRequest get io => null; + // TODO: implement originalMethod + String get originalMethod => null; @override - String get method => - _method ??= (_headers['x-http-method-override'] ?? originalMethod); + // TODO: implement path + String get path => null; @override - String get originalMethod => _originalMethod; + // TODO: implement rawRequest + int get rawRequest => null; @override - Future parseOnce() { - return parseBodyFromStream( - _body?.stream ?? new Stream>.empty(), - contentType == null ? null : new MediaType.parse(contentType.toString()), - uri, - storeOriginalBuffer: app.storeOriginalBuffer, - ); - } + // TODO: implement remoteAddress + InternetAddress get remoteAddress => null; @override - String get path { - if (_path != null) { - return _path; - } else { - var path = __url?.replaceAll(_straySlashes, '') ?? ''; - if (path.isEmpty) path = '/'; - return _path = path; - } - } + // TODO: implement session + HttpSession get session => null; @override - InternetAddress get remoteAddress => _remoteAddress ??= new InternetAddress( - _addressToString(_addressBytes, _addressBytes.length > 4)); - - @override - HttpSession get session { - if (_session != null) return _session; - var dartSessIdCookie = cookies.firstWhere((c) => c.name == 'DARTSESSID', - orElse: () => new Cookie('DARTSESSID', _wings._uuid.v4().toString())); - return _session = _wings._sessions.putIfAbsent(dartSessIdCookie.value, - () => new MockHttpSession(id: dartSessIdCookie.value)); - } - - @override - Uri get uri => _uri ??= Uri.parse(__url); - - @override - bool get xhr => - _headers['x-requested-with']?.trim()?.toLowerCase() == 'xmlhttprequest'; -} - -class _WingsIncomingHeaders extends HttpHeaders { - final WingsRequestContext request; - - _WingsIncomingHeaders(this.request); - - UnsupportedError _unsupported() => - new UnsupportedError('Cannot modify incoming HTTP headers.'); - - @override - List operator [](String name) { - return value(name)?.split(',')?.map((s) => s.trim())?.toList(); - } - - @override - void add(String name, Object value) => throw _unsupported(); - - @override - void clear() => throw _unsupported(); - - @override - void forEach(void Function(String name, List values) f) { - request._headers.forEach((name, value) => - f(name, value.split(',').map((s) => s.trim()).toList())); - } - - @override - void noFolding(String name) => throw _unsupported(); - - @override - void remove(String name, Object value) => throw _unsupported(); - - @override - void removeAll(String name) => throw _unsupported(); - - @override - void set(String name, Object value) => throw _unsupported(); - - @override - String value(String name) => request._headers[name.toLowerCase()]; + // TODO: implement uri + Uri get uri => null; } diff --git a/lib/src/wings_response.dart b/lib/src/wings_response.dart index 40970098..2b9b63ab 100644 --- a/lib/src/wings_response.dart +++ b/lib/src/wings_response.dart @@ -1,156 +1,50 @@ -part of angel_wings; +import 'dart:async'; + +import 'dart:io'; + +import 'package:angel_framework/angel_framework.dart'; class WingsResponseContext extends ResponseContext { - final WingsRequestContext correspondingRequest; - bool _isClosed = false, _useStream = false; - - WingsResponseContext._(this.correspondingRequest); - - AngelWings get _wings => correspondingRequest._wings; - @override - void add(List data) { - if (_isClosed && !_useStream) - throw ResponseContext.closed(); - else if (_useStream) - _wings._send(correspondingRequest, _coerceUint8List(data)); - else - buffer.add(data); - } - - @override - Future close() { - _wings._closeSocket(correspondingRequest); - _isClosed = true; - _useStream = false; - return super.close(); - } - - @override - void end() { - _isClosed = true; - super.end(); + void add(List event) { + // TODO: implement add } @override Future addStream(Stream> stream) { - if (_isClosed && !_useStream) throw ResponseContext.closed(); - var firstStream = useStream(); - - Stream> output = stream; - - if ((firstStream || !headers.containsKey('content-encoding')) && - encoders.isNotEmpty && - correspondingRequest != null) { - var allowedEncodings = - (correspondingRequest.headers['accept-encoding'] ?? []).map((str) { - // Ignore quality specifications in accept-encoding - // ex. gzip;q=0.8 - if (!str.contains(';')) return str; - return str.split(';')[0]; - }); - - for (var encodingName in allowedEncodings) { - Converter, List> encoder; - String key = encodingName; - - if (encoders.containsKey(encodingName)) - encoder = encoders[encodingName]; - else if (encodingName == '*') { - encoder = encoders[key = encoders.keys.first]; - } - - if (encoder != null) { - /* - if (firstStream) { - this.stream.sendHeaders([ - new Header.ascii( - 'content-encoding', headers['content-encoding'] = key) - ]); - } - */ - - output = encoders[key].bind(output); - break; - } - } - } - - return output.forEach( - ((data) => _wings._send(correspondingRequest, _coerceUint8List(data)))); + // TODO: implement addStream + return null; } @override - HttpResponse get io => null; + // TODO: implement buffer + BytesBuilder get buffer => null; @override - bool get isOpen => !_isClosed; + // TODO: implement correspondingRequest + RequestContext get correspondingRequest => null; @override - bool get streaming => _useStream; - - @override - bool useStream() { - if (!_useStream) { - // If this is the first stream added to this response, - // then add headers, status code, etc. - _finalize(); - - willCloseItself = _useStream = _isClosed = true; - releaseCorrespondingRequest(); - return true; - } - - return false; + FutureOr detach() { + // TODO: implement detach + return null; } - /// Write headers, status, etc. to the underlying [stream]. - void _finalize() { - var b = new StringBuffer(); - b.writeln('HTTP/1.1 $statusCode'); - headers['date'] ??= HttpDate.format(new DateTime.now()); + @override + // TODO: implement isBuffered + bool get isBuffered => null; - if (encoders.isNotEmpty && correspondingRequest != null) { - var allowedEncodings = - (correspondingRequest.headers['accept-encoding'] ?? []).map((str) { - // Ignore quality specifications in accept-encoding - // ex. gzip;q=0.8 - if (!str.contains(';')) return str; - return str.split(';')[0]; - }); + @override + // TODO: implement isOpen + bool get isOpen => null; - for (var encodingName in allowedEncodings) { - String key = encodingName; + @override + // TODO: implement rawResponse + get rawResponse => null; - if (encoders.containsKey(encodingName)) { - this.headers['content-encoding'] = key; - break; - } - } - } - - // Add all normal headers - this.headers.forEach((k, v) { - b.writeln('$k: $v'); - }); - - // Persist session ID - if (correspondingRequest._session != null) { - cookies.add(new Cookie('DARTSESSID', correspondingRequest._session.id)); - } - - // Send all cookies - for (var cookie in cookies) { - var value = cookie.toString(); - b.writeln('set-cookie: $value'); - } - - b.writeln(); - - _wings._send( - correspondingRequest, _coerceUint8List(b.toString().codeUnits)); + @override + void useBuffer() { + // TODO: implement useBuffer } -} -Uint8List _coerceUint8List(List list) => - list is Uint8List ? list : new Uint8List.fromList(list); +} \ No newline at end of file diff --git a/lib/src/wings_thread.h b/lib/src/wings_thread.h deleted file mode 100644 index b86fd2f1..00000000 --- a/lib/src/wings_thread.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef ANGEL_WINGS_THREAD_H -#define ANGEL_WINGS_THREAD_H -#include -#include -#include "wings.h" - -typedef struct -{ - Dart_Port port; - WingsServerInfo *serverInfo; -} wings_thread_info; - -typedef struct -{ - bool ipv6; - int sock; - sockaddr addr; - socklen_t addr_len; - Dart_Port port; -} requestInfo; - -void wingsThreadMain(wings_thread_info *info); -void handleRequest(requestInfo *rq); - -#endif \ No newline at end of file diff --git a/lib/src/worker_thread.cc b/lib/src/worker_thread.cc deleted file mode 100644 index 5b06dddf..00000000 --- a/lib/src/worker_thread.cc +++ /dev/null @@ -1,229 +0,0 @@ -#include -#include -#include -#include -#include "wings_thread.h" - -void wingsThreadMain(wings_thread_info *info) -{ - auto *serverInfo = std::move(info->serverInfo); - Dart_Port port = std::move(info->port); - //delete info; - - while (true) - { - //std::unique_lock lock(serverInfo->mutex, std::defer_lock); - std::lock_guard lock(serverInfo->mutex); - - sockaddr client_addr{}; - socklen_t client_addr_len; - - //if (lock.try_lock()) - //{ - int client = accept(serverInfo->sockfd, &client_addr, &client_addr_len); - //lock.unlock(); - - if (client < 0) - { - // send_error(info->port, "Failed to accept client socket."); - return; - } - - //auto rq = std::make_shared(); - 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); - //} - } -} - -int send_notification(http_parser *parser, int code) -{ - //if (parser == nullptr) return 0; - auto *rq = (requestInfo *)parser->data; - //if (rq == nullptr) return 0; - - Dart_CObject first{}; - Dart_CObject second{}; - first.type = second.type = Dart_CObject_kInt64; - first.value.as_int64 = rq->sock; - second.value.as_int64 = code; - - Dart_CObject *list[2]{&first, &second}; - Dart_CObject obj{}; - obj.type = Dart_CObject_kArray; - obj.value.as_array.length = 2; - obj.value.as_array.values = list; - Dart_PostCObject(rq->port, &obj); - return 0; -} - -int send_string(http_parser *parser, char *str, size_t length, int code, bool as_typed_data = false) -{ - //if (parser == nullptr) return 0; - auto *rq = (requestInfo *)parser->data; - char *s = nullptr; - //if (rq == nullptr) return 0; - - Dart_CObject first{}; - Dart_CObject second{}; - Dart_CObject third{}; - first.type = second.type = Dart_CObject_kInt32; - first.value.as_int32 = rq->sock; - second.value.as_int32 = code; - - if (!as_typed_data) - { - s = new char[length + 1]; - memset(s, 0, length + 1); - third.type = Dart_CObject_kString; - //strcpy(s, str); - memcpy(s, str, length); - third.value.as_string = s; - } - else - { - third.type = Dart_CObject_kTypedData; - third.type = Dart_CObject_kTypedData; - third.value.as_typed_data.type = Dart_TypedData_kUint8; - third.value.as_typed_data.length = length; - third.value.as_typed_data.values = (uint8_t *)str; - } - - // Post the string back to Dart... - Dart_CObject *list[3]{&first, &second, &third}; - Dart_CObject obj{}; - obj.type = Dart_CObject_kArray; - obj.value.as_array.length = 3; - obj.value.as_array.values = list; - Dart_PostCObject(rq->port, &obj); - if (s != nullptr) - delete s; - return 0; -} - -int send_oncomplete(http_parser *parser, int code) -{ - //if (parser == nullptr) return 0; - auto *rq = (requestInfo *)parser->data; - //if (rq == nullptr) return 0; - - Dart_CObject sockfd{}; - Dart_CObject command{}; - Dart_CObject method{}; - Dart_CObject major{}; - Dart_CObject minor{}; - Dart_CObject addr{}; - sockfd.type = command.type = method.type = major.type = minor.type = Dart_CObject_kInt32; - addr.type = Dart_CObject_kTypedData; - sockfd.value.as_int32 = rq->sock; - command.value.as_int32 = code; - method.value.as_int32 = parser->method; - major.value.as_int32 = parser->http_major; - minor.value.as_int32 = parser->http_minor; - addr.value.as_typed_data.type = Dart_TypedData_kUint8; - addr.value.as_typed_data.length = rq->addr_len; - - if (rq->ipv6) - { - auto *v6 = (sockaddr_in6 *)&rq->addr; - addr.value.as_typed_data.values = (uint8_t *)v6->sin6_addr.s6_addr; - } - else - { - auto *v4 = (sockaddr_in *)&rq->addr; - addr.value.as_typed_data.values = (uint8_t *)&v4->sin_addr.s_addr; - } - - Dart_CObject *list[6]{&sockfd, &command, &method, &major, &minor, &addr}; - Dart_CObject obj{}; - obj.type = Dart_CObject_kArray; - obj.value.as_array.length = 6; - obj.value.as_array.values = list; - Dart_PostCObject(rq->port, &obj); - //delete parser; - return 0; -} - -//void handleRequest(const std::shared_ptr &rq) -void handleRequest(requestInfo *rq) -{ - size_t len = 100 * 1024, nparsed; - char buf[len]; - ssize_t recved; - memset(buf, 0, len); - - http_parser parser{}; - parser.data = rq; //rq.get(); - http_parser_init(&parser, HTTP_REQUEST); - - http_parser_settings settings{}; - - settings.on_message_begin = [](http_parser *parser) { - //std::cout << "mb" << std::endl; - return send_notification(parser, 0); - }; - - settings.on_message_complete = [](http_parser *parser) { - //std::cout << "mc" << std::endl; - send_oncomplete(parser, 1); - delete (requestInfo *)parser->data; - //std::cout << "deleted rq!" << std::endl; - return 0; - }; - - settings.on_url = [](http_parser *parser, const char *at, size_t length) { - // //std::cout << "url" << std::endl; - return send_string(parser, (char *)at, length, 2); - }; - - settings.on_header_field = [](http_parser *parser, const char *at, size_t length) { - //std::cout << "hf" << std::endl; - return send_string(parser, (char *)at, length, 3); - }; - - settings.on_header_value = [](http_parser *parser, const char *at, size_t length) { - //std::cout << "hv" << std::endl; - return send_string(parser, (char *)at, length, 4); - }; - - settings.on_body = [](http_parser *parser, const char *at, size_t length) { - //std::cout << "body" << std::endl; - return send_string(parser, (char *)at, length, 5, true); - }; - - unsigned int isUpgrade = 0; - - //std::cout << "start" << std::endl; - while ((recved = recv(rq->sock, buf, len, 0)) >= 0) - { - if (isUpgrade) - { - send_string(&parser, buf, (size_t)recved, 7, true); - } - else - { - /* Start up / continue the parser. - * Note we pass recved==0 to signal that EOF has been received. - */ - nparsed = http_parser_execute(&parser, &settings, buf, (size_t)recved); - - if ((isUpgrade = parser.upgrade) == 1) - { - send_notification(&parser, 6); - } - else if (nparsed != recved) - { - //std::cout << "Hm what" << std::endl; - close(rq->sock); - return; - } - } - - memset(buf, 0, len); - } -} \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index 5caadf47..b0db7b73 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,14 +1,10 @@ name: angel_wings +environment: + sdk: ">=2.0.0-dev <3.0.0" dependencies: - angel_framework: ^1.0.0 - build_native: ^0.0.9 + angel_framework: ^2.0.0-alpha + build_native: ^0.0.11 mock_request: ^1.0.0 - pooled_map: ^1.0.0 - uuid: ^1.0.0 dev_dependencies: - angel_static: ^1.3.0 - build_runner: - git: - url: https://github.com/thosakwe/build.git - path: build_runner - ref: experimental-hot-reloading \ No newline at end of file + angel_static: ^2.0.0 + build_runner: ^1.0.0 \ No newline at end of file From 589f38bb11475a99a82dee89009fd2dfb82d571b Mon Sep 17 00:00:00 2001 From: Tobe O Date: Fri, 26 Apr 2019 19:11:58 -0400 Subject: [PATCH 08/28] Redesign --- analysis_options.yaml | 1 + lib/src/native_socket.dart | 60 ++++++++++++++++++++++++++++++++++---- lib/src/wings_driver.dart | 7 ++--- pubspec.yaml | 3 +- 4 files changed, 60 insertions(+), 11 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index eae1e42a..c230cee7 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,3 +1,4 @@ +include: package:pedantic/analysis_options.yaml analyzer: strong-mode: implicit-casts: false \ No newline at end of file diff --git a/lib/src/native_socket.dart b/lib/src/native_socket.dart index c2bbe8e7..ef1b74eb 100644 --- a/lib/src/native_socket.dart +++ b/lib/src/native_socket.dart @@ -1,22 +1,61 @@ import 'dart:async'; +import 'dart:io'; import 'dart:isolate'; import 'dart:typed_data'; import 'dart-ext:angel_wings'; -int bindNativeServerSocket(String addr, int port, SendPort sendPort) - native 'Dart_NativeSocket_bind'; +int bindWingsIPv4ServerSocket(Uint8List address, int port, SendPort sendPort) + native 'Dart_WingsSocket_bind'; + +int bindWingsIPv6ServerSocket(Uint8List address, int port, SendPort sendPort) + native 'Dart_WingsSocket_bind'; + +int getWingsServerSocketPort(int pointer) native 'Dart_WingsSocket_getPort'; void writeToNativeSocket(int fd, Uint8List data) - native 'Dart_NativeSocket_write'; + native 'Dart_WingsSocket_write'; -void closeNativeSocket(int fd) native 'Dart_NativeSocket_close'; +void closeNativeSocketDescriptor(int fd) + native 'Dart_WingsSocket_closeDescriptor'; -class NativeSocket extends Stream { +void closeWingsSocket(int pointer) native 'Dart_WingsSocket_close'; + +class WingsSocket extends Stream { final StreamController _ctrl = StreamController(); final int _pointer; + final RawReceivePort _recv; bool _open = true; + int _port; - NativeSocket._(this._pointer); + WingsSocket._(this._pointer, this._recv) { + _recv.handler = (h) { + if (!_ctrl.isClosed) { + _ctrl.add(h as int); + } + }; + } + + static WingsSocket bind(InternetAddress address, int port) { + var recv = RawReceivePort(); + int ptr; + + try { + if (address.type == InternetAddressType.IPv6) { + ptr = bindWingsIPv6ServerSocket( + Uint8List.fromList(address.rawAddress), port, recv.sendPort); + } else { + ptr = bindWingsIPv4ServerSocket( + Uint8List.fromList(address.rawAddress), port, recv.sendPort); + } + + return WingsSocket._(ptr, recv); + } catch (e) { + recv.close(); + rethrow; + } + } + + int get port => _port ??= getWingsServerSocketPort(_pointer); @override StreamSubscription listen(void Function(int event) onData, @@ -24,4 +63,13 @@ class NativeSocket extends Stream { return _ctrl.stream .listen(onData, onError: onError, cancelOnError: cancelOnError); } + + Future close() async { + if (_open) { + _open = false; + closeWingsSocket(_pointer); + _recv.close(); + await _ctrl.close(); + } + } } diff --git a/lib/src/wings_driver.dart b/lib/src/wings_driver.dart index 41bba0af..50413909 100644 --- a/lib/src/wings_driver.dart +++ b/lib/src/wings_driver.dart @@ -3,19 +3,18 @@ import 'dart:convert'; import 'dart:io' show Cookie; import 'dart:typed_data'; import 'package:angel_framework/angel_framework.dart'; -import 'package:angel_framework/http.dart'; import 'native_socket.dart'; import 'wings_request.dart'; import 'wings_response.dart'; -class AngelWings extends Driver { factory AngelWings(Angel app) { return AngelWings._(app, null); } AngelWings._( - Angel app, Future Function(dynamic, int) serverGenerator) + Angel app, Future Function(dynamic, int) serverGenerator) : super(app, serverGenerator); @override @@ -27,7 +26,7 @@ class AngelWings extends Driver Date: Fri, 26 Apr 2019 19:22:30 -0400 Subject: [PATCH 09/28] Add bind --- example/socket.dart | 6 ++++ lib/angel_wings.dart | 4 +-- lib/src/wings_driver.dart | 4 +-- .../{native_socket.dart => wings_socket.dart} | 36 +++++++++++++------ lib/src/wings_socket.h | 0 5 files changed, 36 insertions(+), 14 deletions(-) create mode 100644 example/socket.dart rename lib/src/{native_socket.dart => wings_socket.dart} (54%) create mode 100644 lib/src/wings_socket.h diff --git a/example/socket.dart b/example/socket.dart new file mode 100644 index 00000000..79d957a4 --- /dev/null +++ b/example/socket.dart @@ -0,0 +1,6 @@ +import 'package:angel_wings/angel_wings.dart'; + +main() async { + var socket = await WingsSocket.bind('127.0.0.1', 3000); + print([socket.port]); +} diff --git a/lib/angel_wings.dart b/lib/angel_wings.dart index 718a257b..9bc403ed 100644 --- a/lib/angel_wings.dart +++ b/lib/angel_wings.dart @@ -1,5 +1,5 @@ export 'src/angel_wings.dart'; -export 'src/native_socket.dart'; export 'src/wings_driver.dart'; export 'src/wings_request.dart'; -export 'src/wings_response.dart'; \ No newline at end of file +export 'src/wings_response.dart'; +export 'src/wings_socket.dart'; \ No newline at end of file diff --git a/lib/src/wings_driver.dart b/lib/src/wings_driver.dart index 50413909..dc87e2ad 100644 --- a/lib/src/wings_driver.dart +++ b/lib/src/wings_driver.dart @@ -3,14 +3,14 @@ import 'dart:convert'; import 'dart:io' show Cookie; import 'dart:typed_data'; import 'package:angel_framework/angel_framework.dart'; -import 'native_socket.dart'; import 'wings_request.dart'; import 'wings_response.dart'; +import 'wings_socket.dart'; class AngelWings extends Driver { factory AngelWings(Angel app) { - return AngelWings._(app, null); + return AngelWings._(app, WingsSocket.bind); } AngelWings._( diff --git a/lib/src/native_socket.dart b/lib/src/wings_socket.dart similarity index 54% rename from lib/src/native_socket.dart rename to lib/src/wings_socket.dart index ef1b74eb..f3365dac 100644 --- a/lib/src/native_socket.dart +++ b/lib/src/wings_socket.dart @@ -4,11 +4,11 @@ import 'dart:isolate'; import 'dart:typed_data'; import 'dart-ext:angel_wings'; -int bindWingsIPv4ServerSocket(Uint8List address, int port, SendPort sendPort) - native 'Dart_WingsSocket_bind'; +int bindWingsIPv4ServerSocket(Uint8List address, int port, bool shared, + int backlog, bool v6Only, SendPort sendPort) native 'Dart_WingsSocket_bind'; -int bindWingsIPv6ServerSocket(Uint8List address, int port, SendPort sendPort) - native 'Dart_WingsSocket_bind'; +int bindWingsIPv6ServerSocket(Uint8List address, int port, bool shared, + int backlog, bool v6Only, SendPort sendPort) native 'Dart_WingsSocket_bind'; int getWingsServerSocketPort(int pointer) native 'Dart_WingsSocket_getPort'; @@ -35,17 +35,33 @@ class WingsSocket extends Stream { }; } - static WingsSocket bind(InternetAddress address, int port) { + static Future bind(address, int port, + {bool shared = false, int backlog = 0, bool v6Only = false}) async { var recv = RawReceivePort(); int ptr; + InternetAddress addr; + + if (address is InternetAddress) { + addr = address; + } else if (address is String) { + var addrs = await InternetAddress.lookup(address); + if (addrs.isNotEmpty) { + addr = addrs[0]; + } else { + throw StateError('Internet address lookup failed: $address'); + } + } else { + throw ArgumentError.value( + address, 'address', 'must be an InternetAddress or String'); + } try { - if (address.type == InternetAddressType.IPv6) { - ptr = bindWingsIPv6ServerSocket( - Uint8List.fromList(address.rawAddress), port, recv.sendPort); + if (addr.type == InternetAddressType.IPv6) { + ptr = bindWingsIPv6ServerSocket(Uint8List.fromList(addr.rawAddress), + port, shared, backlog, v6Only, recv.sendPort); } else { - ptr = bindWingsIPv4ServerSocket( - Uint8List.fromList(address.rawAddress), port, recv.sendPort); + ptr = bindWingsIPv4ServerSocket(Uint8List.fromList(addr.rawAddress), + port, shared, backlog, v6Only, recv.sendPort); } return WingsSocket._(ptr, recv); diff --git a/lib/src/wings_socket.h b/lib/src/wings_socket.h new file mode 100644 index 00000000..e69de29b From 9d9f601fcb8bc509ded230fa07da16ccb2d170bb Mon Sep 17 00:00:00 2001 From: Tobe O Date: Fri, 26 Apr 2019 19:26:12 -0400 Subject: [PATCH 10/28] Add config script --- Makefile | 55 ---------------------------------------------------- Makefile.old | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++ configure | 8 ++++++++ 3 files changed, 63 insertions(+), 55 deletions(-) create mode 100644 Makefile.old create mode 100755 configure diff --git a/Makefile b/Makefile index 68bca647..e69de29b 100644 --- a/Makefile +++ b/Makefile @@ -1,55 +0,0 @@ -CXX=clang -HTTP_PARSER=.dart_tool/build_native/third_party/angel_wings.http_parser -CXX_INCLUDES=-I$(HTTP_PARSER) -I$(DART_SDK)/include - -.PHONY: clean debug macos all - -all: - //printf 'Available targets:\n'\ - ' * `debug` - Builds a debug library on MacOS\n'\ - ' * `example` - Runs example/main.dart in LLDB on MacOS\n'\ - ' * `macos` - Builds a release-mode library on MacOS\n' - -clean: - find lib -name "*.a" -delete - find lib -name "*.o" -delete - find lib -name "*.dylib" -delete - -debug: - $(MAKE) lib/src/libwings.dylib CXXFLAGS="-g -DDEBUG=1" - -macos: - $(MAKE) lib/src/libwings.dylib - -example: debug - lldb -o "target create dart" \ - -o "process launch --stop-at-entry example/main.dart" \ - -o "process handle SIGINT -p true" \ - -o "continue" \ - -lib/src/bind_socket.o: - $(CXX) $(CXXFLAGS) $(CXX_INCLUDES) -std=c++11 -c -o lib/src/bind_socket.o lib/src/bind_socket.cc - -lib/src/http_listener.o: - $(CXX) $(CXXFLAGS) $(CXX_INCLUDES) -std=c++11 -c -o lib/src/http_listener.o lib/src/http_listener.cc - -lib/src/http_parser.o: - $(CXX) $(CXXFLAGS) $(CXX_INCLUDES) -c -o lib/src/http_parser.o $(HTTP_PARSER)/http_parser.c - -lib/src/send.o: - $(CXX) $(CXXFLAGS) $(CXX_INCLUDES) -std=c++11 -c -o lib/src/send.o lib/src/send.cc - -lib/src/util.o: - $(CXX) $(CXXFLAGS) $(CXX_INCLUDES) -std=c++11 -c -o lib/src/util.o lib/src/util.cc - -lib/src/wings.o: - $(CXX) $(CXXFLAGS) $(CXX_INCLUDES) -std=c++11 -c -o lib/src/wings.o lib/src/wings.cc - -lib/src/worker_thread.o: - $(CXX) $(CXXFLAGS) $(CXX_INCLUDES) -std=c++11 -c -o lib/src/worker_thread.o lib/src/worker_thread.cc - -lib/src/libwings.dylib: lib/src/bind_socket.o lib/src/http_listener.o lib/src/http_parser.o lib/src/send.o lib/src/util.o lib/src/wings.o lib/src/worker_thread.o - $(CXX) $(CXXFLAGS) -shared -o lib/src/libwings.dylib -undefined dynamic_lookup -DDART_SHARED_LIB -Wl -fPIC -m64 \ - lib/src/bind_socket.o lib/src/http_listener.o \ - lib/src/http_parser.o lib/src/send.o lib/src/util.o \ - lib/src/wings.o lib/src/worker_thread.o \ No newline at end of file diff --git a/Makefile.old b/Makefile.old new file mode 100644 index 00000000..1a49f460 --- /dev/null +++ b/Makefile.old @@ -0,0 +1,55 @@ +CXX=clang +HTTP_PARSER=.dart_tool/build_native/third_party/angel_wings.http_parser +CXX_INCLUDES=-I$(HTTP_PARSER) -I$(DART_SDK)/include + +.PHONY: clean debug macos all + +all: + printf 'Available targets:\n'\ + ' * `debug` - Builds a debug library on MacOS\n'\ + ' * `example` - Runs example/main.dart in LLDB on MacOS\n'\ + ' * `macos` - Builds a release-mode library on MacOS\n' + +clean: + find lib -name "*.a" -delete + find lib -name "*.o" -delete + find lib -name "*.dylib" -delete + +debug: + $(MAKE) lib/src/libwings.dylib CXXFLAGS="-g -DDEBUG=1" + +macos: + $(MAKE) lib/src/libwings.dylib + +example: debug + lldb -o "target create dart" \ + -o "process launch --stop-at-entry example/main.dart" \ + -o "process handle SIGINT -p true" \ + -o "continue" \ + +lib/src/bind_socket.o: + $(CXX) $(CXXFLAGS) $(CXX_INCLUDES) -std=c++11 -c -o lib/src/bind_socket.o lib/src/bind_socket.cc + +lib/src/http_listener.o: + $(CXX) $(CXXFLAGS) $(CXX_INCLUDES) -std=c++11 -c -o lib/src/http_listener.o lib/src/http_listener.cc + +lib/src/http_parser.o: + $(CXX) $(CXXFLAGS) $(CXX_INCLUDES) -c -o lib/src/http_parser.o $(HTTP_PARSER)/http_parser.c + +lib/src/send.o: + $(CXX) $(CXXFLAGS) $(CXX_INCLUDES) -std=c++11 -c -o lib/src/send.o lib/src/send.cc + +lib/src/util.o: + $(CXX) $(CXXFLAGS) $(CXX_INCLUDES) -std=c++11 -c -o lib/src/util.o lib/src/util.cc + +lib/src/wings.o: + $(CXX) $(CXXFLAGS) $(CXX_INCLUDES) -std=c++11 -c -o lib/src/wings.o lib/src/wings.cc + +lib/src/worker_thread.o: + $(CXX) $(CXXFLAGS) $(CXX_INCLUDES) -std=c++11 -c -o lib/src/worker_thread.o lib/src/worker_thread.cc + +lib/src/libwings.dylib: lib/src/bind_socket.o lib/src/http_listener.o lib/src/http_parser.o lib/src/send.o lib/src/util.o lib/src/wings.o lib/src/worker_thread.o + $(CXX) $(CXXFLAGS) -shared -o lib/src/libwings.dylib -undefined dynamic_lookup -DDART_SHARED_LIB -Wl -fPIC -m64 \ + lib/src/bind_socket.o lib/src/http_listener.o \ + lib/src/http_parser.o lib/src/send.o lib/src/util.o \ + lib/src/wings.o lib/src/worker_thread.o \ No newline at end of file diff --git a/configure b/configure new file mode 100755 index 00000000..bbca3c43 --- /dev/null +++ b/configure @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +HTTP_PARSER_DIR=.dart_tool/http-parser + +if [ ! -d "$HTTP_PARSER_DIR" ] + git clone https://github.com/nodejs/http-parser.git "$HTTP_PARSER_DIR" + exit $? +fi + From e96d37189626ce7f9956c51e4797199ac4ce602c Mon Sep 17 00:00:00 2001 From: Tobe O Date: Sat, 27 Apr 2019 15:12:09 -0400 Subject: [PATCH 11/28] Dart VM is broken --- .gitignore | 10 +- Makefile | 32 +++++ configure | 3 +- lib/angel_wings.dart | 3 +- lib/src/angel_wings.cc | 49 ++++--- lib/src/angel_wings.dart | 3 - lib/src/angel_wings.h | 17 +++ lib/src/bind.cc | 160 +++++++++++++++++++++++ lib/src/libangel_wings.build_native.yaml | 6 +- lib/src/wings_socket.cc | 1 + lib/src/wings_socket.dart | 26 ++-- lib/src/wings_socket.h | 36 +++++ 12 files changed, 308 insertions(+), 38 deletions(-) delete mode 100644 lib/src/angel_wings.dart create mode 100644 lib/src/angel_wings.h create mode 100644 lib/src/bind.cc create mode 100644 lib/src/wings_socket.cc diff --git a/.gitignore b/.gitignore index 3d05881a..8078e48d 100644 --- a/.gitignore +++ b/.gitignore @@ -21,7 +21,11 @@ doc/api/ *.js.map *.o -#*.dylib -#*.a +*.dylib +*.a +*.so *.lib -*.obj \ No newline at end of file +*.obj + +.vscode/ +.idea/ \ No newline at end of file diff --git a/Makefile b/Makefile index e69de29b..a746331f 100644 --- a/Makefile +++ b/Makefile @@ -0,0 +1,32 @@ +CXXFLAGS := $(CXXFLAGS) --std=c++11 -fPIC -DDART_SHARED_LIB=1 -I $(DART_SDK)/include +objects := lib/src/angel_wings.o lib/src/wings_socket.o\ +lib/src/bind.o + +.PHONY: distclean clean + +distclean: clean + rm -rf .dart_tool/http-parser + +clean: + find . -type f -name '*.o' -delete + find . -type f -name '*.obj' -delete + find . -type f -name '*.so' -delete + find . -type f -name '*.dylib' -delete + +mac: lib/src/libangel_wings.dylib + +linux: lib/src/libangel_wings.so + +lib/src/libangel_wings.dylib: $(objects) + +%.dylib: $(objects) + $(CXX) -shared -undefined dynamic_lookup -o $@ $^ + +%.so: $(objects) + $(CXX) -shared -o $@ $^ + +%.o: %.cc lib/src/angel_wings.h + $(CXX) $(CXXFLAGS) -c -o $@ $< + +%.o: %.cc lib/src/angel_wings.h %.h + $(CXX) $(CXXFLAGS) -c -o $@ $< \ No newline at end of file diff --git a/configure b/configure index bbca3c43..2e5d957b 100755 --- a/configure +++ b/configure @@ -2,7 +2,8 @@ HTTP_PARSER_DIR=.dart_tool/http-parser if [ ! -d "$HTTP_PARSER_DIR" ] - git clone https://github.com/nodejs/http-parser.git "$HTTP_PARSER_DIR" +then + git clone --depth 1 https://github.com/nodejs/http-parser.git "$HTTP_PARSER_DIR" exit $? fi diff --git a/lib/angel_wings.dart b/lib/angel_wings.dart index 9bc403ed..c1cda8f7 100644 --- a/lib/angel_wings.dart +++ b/lib/angel_wings.dart @@ -1,5 +1,4 @@ -export 'src/angel_wings.dart'; export 'src/wings_driver.dart'; export 'src/wings_request.dart'; export 'src/wings_response.dart'; -export 'src/wings_socket.dart'; \ No newline at end of file +export 'src/wings_socket.dart'; diff --git a/lib/src/angel_wings.cc b/lib/src/angel_wings.cc index 3cbec736..03731301 100644 --- a/lib/src/angel_wings.cc +++ b/lib/src/angel_wings.cc @@ -2,41 +2,50 @@ #include #include #include - -// Forward declaration of ResolveName function. -Dart_NativeFunction ResolveName(Dart_Handle name, int argc, bool* auto_setup_scope); +#include "angel_wings.h" // The name of the initialization function is the extension name followed // by _Init. -DART_EXPORT Dart_Handle angel_wings_Init(Dart_Handle parent_library) { - if (Dart_IsError(parent_library)) return parent_library; +DART_EXPORT Dart_Handle angel_wings_Init(Dart_Handle parent_library) +{ + if (Dart_IsError(parent_library)) + return parent_library; Dart_Handle result_code = Dart_SetNativeResolver(parent_library, ResolveName, NULL); - if (Dart_IsError(result_code)) return result_code; + if (Dart_IsError(result_code)) + return result_code; return Dart_Null(); } -Dart_Handle HandleError(Dart_Handle handle) { - if (Dart_IsError(handle)) Dart_PropagateError(handle); - return handle; +Dart_Handle HandleError(Dart_Handle handle) +{ + if (Dart_IsError(handle)) + Dart_PropagateError(handle); + return handle; } -// Native functions get their arguments in a Dart_NativeArguments structure -// and return their results with Dart_SetReturnValue. -void SayHello(Dart_NativeArguments arguments) { - std::cout << "Hello, native world!" << std::endl; - Dart_SetReturnValue(arguments, Dart_Null()); -} - -Dart_NativeFunction ResolveName(Dart_Handle name, int argc, bool* auto_setup_scope) { +Dart_NativeFunction ResolveName(Dart_Handle name, int argc, bool *auto_setup_scope) +{ // If we fail, we return NULL, and Dart throws an exception. - if (!Dart_IsString(name)) return NULL; + if (!Dart_IsString(name)) + return NULL; Dart_NativeFunction result = NULL; - const char* cname; + const char *cname; HandleError(Dart_StringToCString(name, &cname)); - if (strcmp("SayHello", cname) == 0) result = SayHello; + if (strcmp("Dart_WingsSocket_bindIPv4", cname) == 0) + result = Dart_WingsSocket_bindIPv4; + if (strcmp("Dart_WingsSocket_bindIPv6", cname) == 0) + result = Dart_WingsSocket_bindIPv6; + if (strcmp("Dart_WingsSocket_getPort", cname) == 0) + result = Dart_WingsSocket_getPort; + if (strcmp("Dart_WingsSocket_write", cname) == 0) + result = Dart_WingsSocket_write; + if (strcmp("Dart_WingsSocket_closeDescriptor", cname) == 0) + result = Dart_WingsSocket_closeDescriptor; + if (strcmp("Dart_WingsSocket_close", cname) == 0) + result = Dart_WingsSocket_close; return result; } \ No newline at end of file diff --git a/lib/src/angel_wings.dart b/lib/src/angel_wings.dart deleted file mode 100644 index ea16d06a..00000000 --- a/lib/src/angel_wings.dart +++ /dev/null @@ -1,3 +0,0 @@ -import 'dart-ext:angel_wings'; - -void sayHello() native "SayHello"; \ No newline at end of file diff --git a/lib/src/angel_wings.h b/lib/src/angel_wings.h new file mode 100644 index 00000000..7da7dc20 --- /dev/null +++ b/lib/src/angel_wings.h @@ -0,0 +1,17 @@ +#ifndef ANGEL_WINGS_WINGS_H +#define ANGEL_WINGS_WINGS_H + +#include +#include "angel_wings.h" + +Dart_NativeFunction ResolveName(Dart_Handle name, int argc, bool *auto_setup_scope); +Dart_Handle HandleError(Dart_Handle handle); + +void Dart_WingsSocket_bindIPv4(Dart_NativeArguments arguments); +void Dart_WingsSocket_bindIPv6(Dart_NativeArguments arguments); +void Dart_WingsSocket_getPort(Dart_NativeArguments arguments); +void Dart_WingsSocket_write(Dart_NativeArguments arguments); +void Dart_WingsSocket_closeDescriptor(Dart_NativeArguments arguments); +void Dart_WingsSocket_close(Dart_NativeArguments arguments); + +#endif \ No newline at end of file diff --git a/lib/src/bind.cc b/lib/src/bind.cc new file mode 100644 index 00000000..9b0351c7 --- /dev/null +++ b/lib/src/bind.cc @@ -0,0 +1,160 @@ +#include +#include +#include +#include +#include +#include +#include "angel_wings.h" +#include "wings_socket.h" +using namespace wings; + +void getWingsSocketInfo(Dart_NativeArguments arguments, WingsSocketInfo *info); + +WingsSocket *wingsFindSocket(Dart_NativeArguments arguments, const WingsSocketInfo &info, int af); + +WingsSocket *wingsBindNewSocket(Dart_NativeArguments arguments, const WingsSocketInfo &info, int af); + +void wingsReturnBound(Dart_NativeArguments arguments, WingsSocket *socket); + +void Dart_WingsSocket_bindIPv4(Dart_NativeArguments arguments) +{ + WingsSocketInfo info; + getWingsSocketInfo(arguments, &info); + WingsSocket *socket = wingsFindSocket(arguments, info, AF_INET); + wingsReturnBound(arguments, socket); +} + +void Dart_WingsSocket_bindIPv6(Dart_NativeArguments arguments) +{ + WingsSocketInfo info; + getWingsSocketInfo(arguments, &info); + WingsSocket *socket = wingsFindSocket(arguments, info, AF_INET6); + wingsReturnBound(arguments, socket); +} + +void wingsReturnBound(Dart_NativeArguments arguments, WingsSocket *socket) +{ + Dart_Port sendPort; + HandleError(Dart_SendPortGetId(socket->getInfo().sendPortHandle, &sendPort)); + socket->incrRef(sendPort); + auto ptr = (uint64_t) socket; + Dart_Handle ptrHandle = Dart_NewIntegerFromUint64(ptr); + Dart_SetReturnValue(arguments, ptrHandle); +} + +void wingsThrowStateError(const char *msg) +{ + Dart_Handle msgHandle = Dart_NewStringFromCString(msg); + Dart_Handle emptyHandle = Dart_NewStringFromCString(""); + Dart_Handle stateErrorHandle = Dart_NewStringFromCString("StateError"); + Dart_Handle dartCoreHandle = Dart_NewStringFromCString("dart:core"); + Dart_Handle dartCore = Dart_LookupLibrary(dartCoreHandle); + Dart_Handle stateError = Dart_GetType(dartCore, stateErrorHandle, 0, nullptr); + Dart_Handle errHandle = Dart_New(stateError, emptyHandle, 1, &msgHandle); + Dart_ThrowException(errHandle); +} + +WingsSocket *wingsFindSocket(Dart_NativeArguments arguments, const WingsSocketInfo &info, int af) +{ + // Find an existing server, if any. + if (info.shared) + { + for (auto *socket : globalSocketList) + { + if (socket->getInfo() == info) + { + return socket; + } + } + } + + return wingsBindNewSocket(arguments, info, af); +} + +WingsSocket *wingsBindNewSocket(Dart_NativeArguments arguments, const WingsSocketInfo &info, int af) +{ + sockaddr *addr; + sockaddr_in v4; + sockaddr_in6 v6; + int ret; + + int sock = socket(af, SOCK_STREAM, IPPROTO_TCP); + + if (sock < 0) + { + wingsThrowStateError("Failed to create socket."); + return nullptr; + } + + int i = 1; + ret = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)); + + if (ret < 0) + { + wingsThrowStateError("Cannot reuse address for socket."); + return nullptr; + } + + // TODO: Only on Mac??? + // ret = setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &i, sizeof(i)); + + // if (ret < 0) + // { + // wingsThrowStateError("Cannot reuse port for socket."); + // return; + // } + + if (af == AF_INET6) + { + v6.sin6_family = AF_INET6; + v6.sin6_port = htons((uint16_t)info.port); + ret = inet_pton(AF_INET6, info.address, &v6.sin6_addr.s6_addr); + if (ret >= 0) + ret = bind(sock, (const sockaddr *)&v6, sizeof(v6)); + } + else + { + v4.sin_family = AF_INET; + v4.sin_port = htons((uint16_t)info.port); + v4.sin_addr.s_addr = inet_addr(info.address); + bind(sock, (const sockaddr *)&v4, sizeof(v4)); + } + + if (ret < 0) + { + wingsThrowStateError("Failed to bind socket."); + return nullptr; + } + + if (listen(sock, SOMAXCONN) < 0) + { + wingsThrowStateError("Failed to set SOMAXCONN on bound socket."); + return nullptr; + } + + if (listen(sock, (int)info.backlog) < 0) + { + wingsThrowStateError("Failed to set backlog on bound socket."); + return nullptr; + } + + auto *out = new WingsSocket(info); + globalSocketList.push_back(out); + return out; +} + +void getWingsSocketInfo(Dart_NativeArguments arguments, WingsSocketInfo *info) +{ + Dart_Handle addressHandle = Dart_GetNativeArgument(arguments, 0); + Dart_Handle portHandle = Dart_GetNativeArgument(arguments, 1); + Dart_Handle sharedHandle = Dart_GetNativeArgument(arguments, 2); + Dart_Handle backlogHandle = Dart_GetNativeArgument(arguments, 3); + Dart_Handle v6OnlyHandle = Dart_GetNativeArgument(arguments, 4); + info->sendPortHandle = Dart_GetNativeArgument(arguments, 5); + + HandleError(Dart_StringToCString(addressHandle, &info->address)); + HandleError(Dart_IntegerToUint64(portHandle, &info->port)); + HandleError(Dart_BooleanValue(sharedHandle, &info->shared)); + HandleError(Dart_IntegerToUint64(backlogHandle, &info->backlog)); + HandleError(Dart_BooleanValue(v6OnlyHandle, &info->v6Only)); +} diff --git a/lib/src/libangel_wings.build_native.yaml b/lib/src/libangel_wings.build_native.yaml index 855e4974..f260efe8 100644 --- a/lib/src/libangel_wings.build_native.yaml +++ b/lib/src/libangel_wings.build_native.yaml @@ -1,2 +1,6 @@ +include: + - lib/src sources: - - angel_wings|lib/src/angel_wings.cc \ No newline at end of file + - angel_wings|lib/src/angel_wings.cc + - angel_wings|lib/src/bind.cc + - angel_wings|lib/src/wings_socket.cc \ No newline at end of file diff --git a/lib/src/wings_socket.cc b/lib/src/wings_socket.cc new file mode 100644 index 00000000..3c11c6bb --- /dev/null +++ b/lib/src/wings_socket.cc @@ -0,0 +1 @@ +#include "wings_socket.h" \ No newline at end of file diff --git a/lib/src/wings_socket.dart b/lib/src/wings_socket.dart index f3365dac..193c0586 100644 --- a/lib/src/wings_socket.dart +++ b/lib/src/wings_socket.dart @@ -4,11 +4,21 @@ import 'dart:isolate'; import 'dart:typed_data'; import 'dart-ext:angel_wings'; -int bindWingsIPv4ServerSocket(Uint8List address, int port, bool shared, - int backlog, bool v6Only, SendPort sendPort) native 'Dart_WingsSocket_bind'; +int bindWingsIPv4ServerSocket( + String address, + int port, + bool shared, + int backlog, + bool v6Only, + SendPort sendPort) native 'Dart_WingsSocket_bindIPv4'; -int bindWingsIPv6ServerSocket(Uint8List address, int port, bool shared, - int backlog, bool v6Only, SendPort sendPort) native 'Dart_WingsSocket_bind'; +int bindWingsIPv6ServerSocket( + String address, + int port, + bool shared, + int backlog, + bool v6Only, + SendPort sendPort) native 'Dart_WingsSocket_bindIPV6'; int getWingsServerSocketPort(int pointer) native 'Dart_WingsSocket_getPort'; @@ -57,11 +67,11 @@ class WingsSocket extends Stream { try { if (addr.type == InternetAddressType.IPv6) { - ptr = bindWingsIPv6ServerSocket(Uint8List.fromList(addr.rawAddress), - port, shared, backlog, v6Only, recv.sendPort); + ptr = bindWingsIPv6ServerSocket( + addr.address, port, shared, backlog, v6Only, recv.sendPort); } else { - ptr = bindWingsIPv4ServerSocket(Uint8List.fromList(addr.rawAddress), - port, shared, backlog, v6Only, recv.sendPort); + ptr = bindWingsIPv4ServerSocket( + addr.address, port, shared, backlog, v6Only, recv.sendPort); } return WingsSocket._(ptr, recv); diff --git a/lib/src/wings_socket.h b/lib/src/wings_socket.h index e69de29b..6c454c66 100644 --- a/lib/src/wings_socket.h +++ b/lib/src/wings_socket.h @@ -0,0 +1,36 @@ +#ifndef WINGS_SOCKET_H +#define WINGS_SOCKET_H +#include +#include + +namespace wings +{ +struct WingsSocketInfo +{ + const char *address; + uint64_t port; + bool shared; + uint64_t backlog; + bool v6Only; + Dart_Handle sendPortHandle; + bool operator==(const WingsSocketInfo& other) const; +}; + +class WingsSocket +{ + public: + explicit WingsSocket(const WingsSocketInfo& info); + void incrRef(Dart_Port port); + const WingsSocketInfo getInfo() const; + + private: + WingsSocketInfo info; + int sockfd; + int refCount; + std::vector sendPorts; +}; + +extern std::vector globalSocketList; +} // namespace wings + +#endif \ No newline at end of file From 2738fa7e5840ecad572187693cd8a2a0dc8a9a69 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Mon, 29 Apr 2019 01:10:27 -0400 Subject: [PATCH 12/28] Basic listen works --- Makefile | 7 +++++-- lib/src/bind.cc | 4 ++-- lib/src/util.cc | 21 +++++++++++++++++++++ lib/src/wings_socket.cc | 28 +++++++++++++++++++++++++++- lib/src/wings_socket.h | 4 ++-- 5 files changed, 57 insertions(+), 7 deletions(-) create mode 100644 lib/src/util.cc diff --git a/Makefile b/Makefile index a746331f..c7628414 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ CXXFLAGS := $(CXXFLAGS) --std=c++11 -fPIC -DDART_SHARED_LIB=1 -I $(DART_SDK)/include objects := lib/src/angel_wings.o lib/src/wings_socket.o\ -lib/src/bind.o +lib/src/bind.o lib/src/util.o .PHONY: distclean clean @@ -13,10 +13,13 @@ clean: find . -type f -name '*.so' -delete find . -type f -name '*.dylib' -delete -mac: lib/src/libangel_wings.dylib +mac: libangel_wings.dylib linux: lib/src/libangel_wings.so +libangel_wings.dylib: lib/src/libangel_wings.dylib + cp $< $@ + lib/src/libangel_wings.dylib: $(objects) %.dylib: $(objects) diff --git a/lib/src/bind.cc b/lib/src/bind.cc index 9b0351c7..757cdb63 100644 --- a/lib/src/bind.cc +++ b/lib/src/bind.cc @@ -37,7 +37,7 @@ void wingsReturnBound(Dart_NativeArguments arguments, WingsSocket *socket) Dart_Port sendPort; HandleError(Dart_SendPortGetId(socket->getInfo().sendPortHandle, &sendPort)); socket->incrRef(sendPort); - auto ptr = (uint64_t) socket; + auto ptr = (uint64_t)socket; Dart_Handle ptrHandle = Dart_NewIntegerFromUint64(ptr); Dart_SetReturnValue(arguments, ptrHandle); } @@ -138,7 +138,7 @@ WingsSocket *wingsBindNewSocket(Dart_NativeArguments arguments, const WingsSocke return nullptr; } - auto *out = new WingsSocket(info); + auto *out = new WingsSocket(sock, info); globalSocketList.push_back(out); return out; } diff --git a/lib/src/util.cc b/lib/src/util.cc new file mode 100644 index 00000000..51fbd532 --- /dev/null +++ b/lib/src/util.cc @@ -0,0 +1,21 @@ +#include "angel_wings.h" + +void Dart_WingsSocket_getPort(Dart_NativeArguments arguments) +{ + // TODO: Actually do something. +} + +void Dart_WingsSocket_write(Dart_NativeArguments arguments) +{ + // TODO: Actually do something. +} + +void Dart_WingsSocket_closeDescriptor(Dart_NativeArguments arguments) +{ + // TODO: Actually do something. +} + +void Dart_WingsSocket_close(Dart_NativeArguments arguments) +{ + // TODO: Actually do something. +} \ No newline at end of file diff --git a/lib/src/wings_socket.cc b/lib/src/wings_socket.cc index 3c11c6bb..73f04f2a 100644 --- a/lib/src/wings_socket.cc +++ b/lib/src/wings_socket.cc @@ -1 +1,27 @@ -#include "wings_socket.h" \ No newline at end of file +#include +#include "wings_socket.h" +using namespace wings; + +std::vector wings::globalSocketList; + +bool WingsSocketInfo::operator==(const WingsSocketInfo &other) const +{ + return (strcmp(address, other.address) == 0) && + port == other.port; +} + +WingsSocket::WingsSocket(int sockfd, const WingsSocketInfo &info) : sockfd(sockfd), info(info) +{ + refCount = 0; +} + +void WingsSocket::incrRef(Dart_Port port) +{ + refCount++; + sendPorts.push_back(port); +} + +const WingsSocketInfo &WingsSocket::getInfo() const +{ + return info; +} \ No newline at end of file diff --git a/lib/src/wings_socket.h b/lib/src/wings_socket.h index 6c454c66..9a257763 100644 --- a/lib/src/wings_socket.h +++ b/lib/src/wings_socket.h @@ -19,9 +19,9 @@ struct WingsSocketInfo class WingsSocket { public: - explicit WingsSocket(const WingsSocketInfo& info); + explicit WingsSocket(int sockfd, const WingsSocketInfo& info); void incrRef(Dart_Port port); - const WingsSocketInfo getInfo() const; + const WingsSocketInfo& getInfo() const; private: WingsSocketInfo info; From 7f70420991af5c20ddd9eeda8606b9d4399ff174 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Mon, 29 Apr 2019 01:13:45 -0400 Subject: [PATCH 13/28] Add getPort --- lib/src/util.cc | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/src/util.cc b/lib/src/util.cc index 51fbd532..854003cc 100644 --- a/lib/src/util.cc +++ b/lib/src/util.cc @@ -1,8 +1,16 @@ #include "angel_wings.h" +#include "wings_socket.h" +using namespace wings; void Dart_WingsSocket_getPort(Dart_NativeArguments arguments) { - // TODO: Actually do something. + uint64_t ptr; + Dart_Handle pointerHandle = Dart_GetNativeArgument(arguments, 0); + HandleError(Dart_IntegerToUint64(pointerHandle, &ptr)); + + auto* socket = (WingsSocket*) ptr; + auto outHandle = Dart_NewIntegerFromUint64(socket->getInfo().port); + Dart_SetReturnValue(arguments, outHandle); } void Dart_WingsSocket_write(Dart_NativeArguments arguments) From 71d8a07513aa39c7917b496671a70f6f396bb7cb Mon Sep 17 00:00:00 2001 From: Tobe O Date: Mon, 29 Apr 2019 01:55:02 -0400 Subject: [PATCH 14/28] Solid listening plan --- Makefile | 2 +- example/socket.dart | 6 ++++- lib/src/angel_wings.cc | 2 ++ lib/src/angel_wings.h | 1 + lib/src/bind.cc | 6 ----- lib/src/util.cc | 17 ++++++++++-- lib/src/wings_socket.cc | 56 +++++++++++++++++++++++++++++++++++++++ lib/src/wings_socket.dart | 7 +++++ lib/src/wings_socket.h | 46 ++++++++++++++++++++------------ 9 files changed, 116 insertions(+), 27 deletions(-) diff --git a/Makefile b/Makefile index c7628414..6719871c 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -CXXFLAGS := $(CXXFLAGS) --std=c++11 -fPIC -DDART_SHARED_LIB=1 -I $(DART_SDK)/include +CXXFLAGS := $(CXXFLAGS) --std=c++14 -fPIC -DDART_SHARED_LIB=1 -I $(DART_SDK)/include objects := lib/src/angel_wings.o lib/src/wings_socket.o\ lib/src/bind.o lib/src/util.o diff --git a/example/socket.dart b/example/socket.dart index 79d957a4..1bc5be84 100644 --- a/example/socket.dart +++ b/example/socket.dart @@ -2,5 +2,9 @@ import 'package:angel_wings/angel_wings.dart'; main() async { var socket = await WingsSocket.bind('127.0.0.1', 3000); - print([socket.port]); + + await for (var fd in socket) { + print('FD: $fd'); + closeNativeSocketDescriptor(fd); + } } diff --git a/lib/src/angel_wings.cc b/lib/src/angel_wings.cc index 03731301..497553f6 100644 --- a/lib/src/angel_wings.cc +++ b/lib/src/angel_wings.cc @@ -47,5 +47,7 @@ Dart_NativeFunction ResolveName(Dart_Handle name, int argc, bool *auto_setup_sco result = Dart_WingsSocket_closeDescriptor; if (strcmp("Dart_WingsSocket_close", cname) == 0) result = Dart_WingsSocket_close; + if (strcmp("Dart_WingsSocket_listen", cname) == 0) + result = Dart_WingsSocket_listen; return result; } \ No newline at end of file diff --git a/lib/src/angel_wings.h b/lib/src/angel_wings.h index 7da7dc20..51ef882d 100644 --- a/lib/src/angel_wings.h +++ b/lib/src/angel_wings.h @@ -13,5 +13,6 @@ void Dart_WingsSocket_getPort(Dart_NativeArguments arguments); void Dart_WingsSocket_write(Dart_NativeArguments arguments); void Dart_WingsSocket_closeDescriptor(Dart_NativeArguments arguments); void Dart_WingsSocket_close(Dart_NativeArguments arguments); +void Dart_WingsSocket_listen(Dart_NativeArguments arguments); #endif \ No newline at end of file diff --git a/lib/src/bind.cc b/lib/src/bind.cc index 757cdb63..dcd4c686 100644 --- a/lib/src/bind.cc +++ b/lib/src/bind.cc @@ -1,9 +1,3 @@ -#include -#include -#include -#include -#include -#include #include "angel_wings.h" #include "wings_socket.h" using namespace wings; diff --git a/lib/src/util.cc b/lib/src/util.cc index 854003cc..e627b1c5 100644 --- a/lib/src/util.cc +++ b/lib/src/util.cc @@ -2,13 +2,23 @@ #include "wings_socket.h" using namespace wings; +void Dart_WingsSocket_listen(Dart_NativeArguments arguments) +{ + uint64_t ptr; + Dart_Handle pointerHandle = Dart_GetNativeArgument(arguments, 0); + HandleError(Dart_IntegerToUint64(pointerHandle, &ptr)); + + auto *socket = (WingsSocket *)ptr; + socket->start(arguments); +} + void Dart_WingsSocket_getPort(Dart_NativeArguments arguments) { uint64_t ptr; Dart_Handle pointerHandle = Dart_GetNativeArgument(arguments, 0); HandleError(Dart_IntegerToUint64(pointerHandle, &ptr)); - auto* socket = (WingsSocket*) ptr; + auto *socket = (WingsSocket *)ptr; auto outHandle = Dart_NewIntegerFromUint64(socket->getInfo().port); Dart_SetReturnValue(arguments, outHandle); } @@ -20,7 +30,10 @@ void Dart_WingsSocket_write(Dart_NativeArguments arguments) void Dart_WingsSocket_closeDescriptor(Dart_NativeArguments arguments) { - // TODO: Actually do something. + int64_t fd; + Dart_Handle fdHandle = Dart_GetNativeArgument(arguments, 0); + HandleError(Dart_IntegerToInt64(fdHandle, &fd)); + close(fd); } void Dart_WingsSocket_close(Dart_NativeArguments arguments) diff --git a/lib/src/wings_socket.cc b/lib/src/wings_socket.cc index 73f04f2a..bd532b1b 100644 --- a/lib/src/wings_socket.cc +++ b/lib/src/wings_socket.cc @@ -1,4 +1,5 @@ #include +#include #include "wings_socket.h" using namespace wings; @@ -13,6 +14,7 @@ bool WingsSocketInfo::operator==(const WingsSocketInfo &other) const WingsSocket::WingsSocket(int sockfd, const WingsSocketInfo &info) : sockfd(sockfd), info(info) { refCount = 0; + workerThread = nullptr; } void WingsSocket::incrRef(Dart_Port port) @@ -24,4 +26,58 @@ void WingsSocket::incrRef(Dart_Port port) const WingsSocketInfo &WingsSocket::getInfo() const { return info; +} + +void WingsSocket::start(Dart_NativeArguments arguments) +{ + // if (workerThread == nullptr) + // { + // workerThread = std::make_unique(threadCallback, this); + // } + Dart_Port service_port = + Dart_NewNativePort("WingsThreadCallback", &threadCallback, true); + Dart_Handle send_port = Dart_NewSendPort(service_port); + Dart_SetReturnValue(arguments, send_port); +} + +void WingsSocket::threadCallback(Dart_Port dest_port_id, + Dart_CObject *message) +{ + + WingsSocket *socket = nullptr; + Dart_Port outPort = message->value.as_array.values[0]->value.as_send_port.id; + Dart_CObject* ptrArg = message->value.as_array.values[1]; + + if (ptrArg->type == Dart_CObject_kInt32) + { + auto as64 = (int64_t)ptrArg->value.as_int32; + socket = (WingsSocket *)as64; + } + else + { + socket = (WingsSocket *)ptrArg->value.as_int64; + } + + if (socket != nullptr) + { + int sock; + unsigned long index = 0; + sockaddr addr; + socklen_t len; + + if ((sock = accept(socket->sockfd, &addr, &len)) != -1) + { + Dart_CObject obj; + obj.type = Dart_CObject_kInt64; + obj.value.as_int64 = sock; + Dart_PostCObject(outPort, &obj); + // Dispatch the fd to the next listener. + // auto &ports = socket->sendPorts; + // Dart_Port port = ports.at(index++); + // if (index >= ports.size()) + // index = 0; + // Dart_Handle intHandle = Dart_NewInteger(sock); + // Dart_Post(port, intHandle); + } + } } \ No newline at end of file diff --git a/lib/src/wings_socket.dart b/lib/src/wings_socket.dart index 193c0586..e5f29d93 100644 --- a/lib/src/wings_socket.dart +++ b/lib/src/wings_socket.dart @@ -28,21 +28,28 @@ void writeToNativeSocket(int fd, Uint8List data) void closeNativeSocketDescriptor(int fd) native 'Dart_WingsSocket_closeDescriptor'; +SendPort wingsSocketListen(int pointer) native 'Dart_WingsSocket_listen'; + void closeWingsSocket(int pointer) native 'Dart_WingsSocket_close'; class WingsSocket extends Stream { final StreamController _ctrl = StreamController(); + SendPort _acceptor; final int _pointer; final RawReceivePort _recv; bool _open = true; int _port; WingsSocket._(this._pointer, this._recv) { + _acceptor = wingsSocketListen(_pointer); _recv.handler = (h) { if (!_ctrl.isClosed) { _ctrl.add(h as int); + _acceptor.send([_recv.sendPort, _pointer]); } }; + + _acceptor.send([_recv.sendPort, _pointer]); } static Future bind(address, int port, diff --git a/lib/src/wings_socket.h b/lib/src/wings_socket.h index 9a257763..a14cddf4 100644 --- a/lib/src/wings_socket.h +++ b/lib/src/wings_socket.h @@ -1,36 +1,48 @@ #ifndef WINGS_SOCKET_H #define WINGS_SOCKET_H +#include +#include +#include +#include +#include +#include #include +#include +#include +#include #include namespace wings { struct WingsSocketInfo { - const char *address; - uint64_t port; - bool shared; - uint64_t backlog; - bool v6Only; - Dart_Handle sendPortHandle; - bool operator==(const WingsSocketInfo& other) const; + const char *address; + uint64_t port; + bool shared; + uint64_t backlog; + bool v6Only; + Dart_Handle sendPortHandle; + bool operator==(const WingsSocketInfo &other) const; }; class WingsSocket { - public: - explicit WingsSocket(int sockfd, const WingsSocketInfo& info); - void incrRef(Dart_Port port); - const WingsSocketInfo& getInfo() const; +public: + explicit WingsSocket(int sockfd, const WingsSocketInfo &info); + void incrRef(Dart_Port port); + const WingsSocketInfo &getInfo() const; + void start(Dart_NativeArguments arguments); - private: - WingsSocketInfo info; - int sockfd; - int refCount; - std::vector sendPorts; +private: + static void threadCallback(Dart_Port dest_port_id, Dart_CObject *message); + WingsSocketInfo info; + int sockfd; + int refCount; + std::unique_ptr workerThread; + std::vector sendPorts; }; -extern std::vector globalSocketList; +extern std::vector globalSocketList; } // namespace wings #endif \ No newline at end of file From 18cb8749ee770e07b678e22d6b979799878b7233 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Mon, 29 Apr 2019 04:22:36 -0400 Subject: [PATCH 15/28] Almost done --- Makefile | 8 +- example/main.dart | 21 +++- example/socket.dart | 21 +++- lib/src/angel_wings.cc | 2 + lib/src/angel_wings.h | 3 + lib/src/http.cc | 142 ++++++++++++++++++++++++ lib/src/util.cc | 12 +- lib/src/wings_driver.dart | 18 ++- lib/src/wings_request.dart | 170 +++++++++++++++++++++++----- lib/src/wings_response.dart | 213 +++++++++++++++++++++++++++++++----- lib/src/wings_socket.cc | 31 +++++- lib/src/wings_socket.dart | 23 +++- 12 files changed, 589 insertions(+), 75 deletions(-) create mode 100644 lib/src/http.cc diff --git a/Makefile b/Makefile index 6719871c..07bb2b86 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,8 @@ -CXXFLAGS := $(CXXFLAGS) --std=c++14 -fPIC -DDART_SHARED_LIB=1 -I $(DART_SDK)/include -objects := lib/src/angel_wings.o lib/src/wings_socket.o\ -lib/src/bind.o lib/src/util.o +override CXXFLAGS := $(CXXFLAGS) --std=c++14 -fPIC -DDART_SHARED_LIB=1 -I $(DART_SDK)/include \ +-I .dart_tool +objects := lib/src/angel_wings.o lib/src/wings_socket.o \ +lib/src/bind.o lib/src/util.o lib/src/http.o \ +.dart_tool/http-parser/http_parser.o .PHONY: distclean clean diff --git a/example/main.dart b/example/main.dart index 9c64ef37..b14aeae2 100644 --- a/example/main.dart +++ b/example/main.dart @@ -1,7 +1,26 @@ import 'package:angel_framework/angel_framework.dart'; +import 'package:angel_static/angel_static.dart'; import 'package:angel_wings/angel_wings.dart'; +import 'package:file/local.dart'; +import 'package:logging/logging.dart'; main() async { var app = Angel(); var wings = AngelWings(app); -} \ No newline at end of file + var fs = LocalFileSystem(); + var vDir = CachingVirtualDirectory(app, fs, + source: fs.currentDirectory, allowDirectoryListing: true); + app.logger = Logger('wings') + ..onRecord.listen((rec) { + print(rec); + if (rec.error != null) print(rec.error); + if (rec.stackTrace != null) print(rec.stackTrace); + }); + + app.get('/', (req, res) => 'WINGS!!!'); + app.fallback(vDir.handleRequest); + app.fallback((req, res) => throw AngelHttpException.notFound()); + + await wings.startServer('127.0.0.1', 3000); + print('Listening at http://localhost:3000'); +} diff --git a/example/socket.dart b/example/socket.dart index 1bc5be84..5c827772 100644 --- a/example/socket.dart +++ b/example/socket.dart @@ -1,10 +1,27 @@ +import 'dart:convert'; +import 'dart:typed_data'; +import 'package:angel_framework/angel_framework.dart'; import 'package:angel_wings/angel_wings.dart'; main() async { + var app = Angel(); var socket = await WingsSocket.bind('127.0.0.1', 3000); + print('Listening at http://localhost:3000'); await for (var fd in socket) { - print('FD: $fd'); - closeNativeSocketDescriptor(fd); + var response = ''' +HTTP/1.1 200 Not Found\r +Date: Fri, 31 Dec 1999 23:59:59 GMT\r +server: wings-test\r\n\r +Nope, nothing's here! +\r\n\r +'''; + var bytes = utf8.encode(response); + var data = Uint8List.fromList(bytes); + var rq = await WingsRequestContext.from(app, fd); + print('Yay: $rq'); + print(rq.headers); + writeToNativeSocket(fd.fileDescriptor, data); + closeNativeSocketDescriptor(fd.fileDescriptor); } } diff --git a/lib/src/angel_wings.cc b/lib/src/angel_wings.cc index 497553f6..2b19cd6b 100644 --- a/lib/src/angel_wings.cc +++ b/lib/src/angel_wings.cc @@ -49,5 +49,7 @@ Dart_NativeFunction ResolveName(Dart_Handle name, int argc, bool *auto_setup_sco result = Dart_WingsSocket_close; if (strcmp("Dart_WingsSocket_listen", cname) == 0) result = Dart_WingsSocket_listen; + if (strcmp("Dart_WingsSocket_parseHttp", cname) == 0) + result = Dart_WingsSocket_parseHttp; return result; } \ No newline at end of file diff --git a/lib/src/angel_wings.h b/lib/src/angel_wings.h index 51ef882d..65397365 100644 --- a/lib/src/angel_wings.h +++ b/lib/src/angel_wings.h @@ -2,6 +2,7 @@ #define ANGEL_WINGS_WINGS_H #include +#include #include "angel_wings.h" Dart_NativeFunction ResolveName(Dart_Handle name, int argc, bool *auto_setup_scope); @@ -14,5 +15,7 @@ void Dart_WingsSocket_write(Dart_NativeArguments arguments); void Dart_WingsSocket_closeDescriptor(Dart_NativeArguments arguments); void Dart_WingsSocket_close(Dart_NativeArguments arguments); void Dart_WingsSocket_listen(Dart_NativeArguments arguments); +void Dart_WingsSocket_parseHttp(Dart_NativeArguments arguments); +void wingsHttpCallback(Dart_Port dest_port_id, Dart_CObject *message); #endif \ No newline at end of file diff --git a/lib/src/http.cc b/lib/src/http.cc new file mode 100644 index 00000000..ecf478ba --- /dev/null +++ b/lib/src/http.cc @@ -0,0 +1,142 @@ +#include +#include +#include "angel_wings.h" +#include "wings_socket.h" +using namespace wings; + +void Dart_WingsSocket_parseHttp(Dart_NativeArguments arguments) +{ + Dart_Port service_port = + Dart_NewNativePort("WingsHttpCallback", &wingsHttpCallback, true); + Dart_Handle send_port = Dart_NewSendPort(service_port); + Dart_SetReturnValue(arguments, send_port); +} + +void wingsHttpCallback(Dart_Port dest_port_id, Dart_CObject *message) +{ + int64_t fd = -1; + Dart_Port outPort = message->value.as_array.values[0]->value.as_send_port.id; + Dart_CObject *fdArg = message->value.as_array.values[1]; + +#define thePort (*((Dart_Port *)parser->data)) +#define sendInt(n) \ + Dart_CObject obj; \ + obj.type = Dart_CObject_kInt64; \ + obj.value.as_int64 = (n); \ + Dart_PostCObject(thePort, &obj); +#define sendString() \ + if (length > 0) \ + { \ + std::string str(at, length); \ + Dart_CObject obj; \ + obj.type = Dart_CObject_kString; \ + obj.value.as_string = (char *)str.c_str(); \ + Dart_PostCObject(thePort, &obj); \ + } + + if (fdArg->type == Dart_CObject_kInt32) + { + fd = (int64_t)fdArg->value.as_int32; + } + else + { + fd = fdArg->value.as_int64; + } + + if (fd != -1) + { + http_parser_settings settings; + + settings.on_message_begin = [](http_parser *parser) { + return 0; + }; + + settings.on_headers_complete = [](http_parser *parser) { + { + sendInt(0); + } + { + sendInt(parser->method); + } + return 0; + }; + + settings.on_message_complete = [](http_parser *parser) { + sendInt(1); + return 0; + }; + + settings.on_chunk_complete = [](http_parser *parser) { + return 0; + }; + + settings.on_chunk_header = [](http_parser *parser) { + return 0; + }; + + settings.on_url = [](http_parser *parser, const char *at, size_t length) { + sendString(); + return 0; + }; + + settings.on_header_field = [](http_parser *parser, const char *at, size_t length) { + sendString(); + return 0; + }; + + settings.on_header_value = [](http_parser *parser, const char *at, size_t length) { + sendString(); + return 0; + }; + + settings.on_body = [](http_parser *parser, const char *at, size_t length) { + sendString(); + return 0; + }; + + size_t len = 80 * 1024, nparsed = 0; + char buf[len]; + ssize_t recved = 0; + memset(buf, 0, sizeof(buf)); + // http_parser parser; + auto *parser = (http_parser *)malloc(sizeof(http_parser)); + http_parser_init(parser, HTTP_BOTH); + parser->data = &outPort; + + while ((recved = recv(fd, buf, len, 0)) >= 0) + { + if (false) // (isUpgrade) + { + // send_string(&parser, buf, (size_t)recved, 7, true); + } + else + { + /* Start up / continue the parser. + * Note we pass recved==0 to signal that EOF has been received. + */ + nparsed = http_parser_execute(parser, &settings, buf, recved); + + if (nparsed != recved) + { + // TODO: End it...! + } + else if (recved == 0) + { + break; + } + + // if ((isUpgrade = parser.upgrade) == 1) + // { + // send_notification(&parser, 6); + // } + // else if (nparsed != recved) + // { + // close(rq->sock); + // return; + // } + } + + // memset(buf, 0, len); + } + } +} \ No newline at end of file diff --git a/lib/src/util.cc b/lib/src/util.cc index e627b1c5..69b6b88c 100644 --- a/lib/src/util.cc +++ b/lib/src/util.cc @@ -1,3 +1,4 @@ +#include #include "angel_wings.h" #include "wings_socket.h" using namespace wings; @@ -25,7 +26,16 @@ void Dart_WingsSocket_getPort(Dart_NativeArguments arguments) void Dart_WingsSocket_write(Dart_NativeArguments arguments) { - // TODO: Actually do something. + int64_t fd; + void *data; + Dart_TypedData_Type type; + intptr_t len; + Dart_Handle fdHandle = Dart_GetNativeArgument(arguments, 0); + Dart_Handle dataHandle = Dart_GetNativeArgument(arguments, 1); + HandleError(Dart_IntegerToInt64(fdHandle, &fd)); + HandleError(Dart_TypedDataAcquireData(dataHandle, &type, &data, &len)); + write(fd, data, len); + HandleError(Dart_TypedDataReleaseData(dataHandle)); } void Dart_WingsSocket_closeDescriptor(Dart_NativeArguments arguments) diff --git a/lib/src/wings_driver.dart b/lib/src/wings_driver.dart index dc87e2ad..bc4bff0e 100644 --- a/lib/src/wings_driver.dart +++ b/lib/src/wings_driver.dart @@ -7,8 +7,8 @@ import 'wings_request.dart'; import 'wings_response.dart'; import 'wings_socket.dart'; -class AngelWings extends Driver { +class AngelWings extends Driver { factory AngelWings(Angel app) { return AngelWings._(app, WingsSocket.bind); } @@ -31,21 +31,19 @@ class AngelWings extends Driver createRequestContext(int request, int response) { - // TODO: implement createRequestContext - return null; + Future createRequestContext(WingsClientSocket request, int response) { + return WingsRequestContext.from(app, request); } @override - Future createResponseContext(int request, int response, + Future createResponseContext(WingsClientSocket request, int response, [WingsRequestContext correspondingRequest]) { - // TODO: implement createResponseContext - return null; + return Future.value(WingsResponseContext(request.fileDescriptor, correspondingRequest)); } @override - Stream createResponseStreamFromRawRequest(int request) { - return Stream.fromIterable([request]); + Stream createResponseStreamFromRawRequest(WingsClientSocket request) { + return Stream.fromIterable([request.fileDescriptor]); } @override diff --git a/lib/src/wings_request.dart b/lib/src/wings_request.dart index e890bc40..6b97e8e6 100644 --- a/lib/src/wings_request.dart +++ b/lib/src/wings_request.dart @@ -1,53 +1,173 @@ +import 'dart:async'; import 'dart:io'; -import 'package:angel_container/src/container.dart'; +import 'dart:isolate'; +import 'package:angel_container/angel_container.dart'; import 'package:angel_framework/angel_framework.dart'; +import 'package:mock_request/mock_request.dart'; +import 'wings_socket.dart'; -class WingsRequestContext extends RequestContext { - @override - // TODO: implement body - Stream> get body => null; +enum _ParseState { method, url, headerField, headerValue, body } + +final RegExp _straySlashes = new RegExp(r'(^/+)|(/+$)'); + +class WingsRequestContext extends RequestContext { + final WingsClientSocket rawRequest; + final Container container; + + final StreamController> _body = StreamController(); + List _cookies, __cookies; + final LockableMockHttpHeaders _headers = LockableMockHttpHeaders(); + final RawReceivePort _recv; + InternetAddress _remoteAddress; + String _method, _override, _path; + Uri _uri; @override - // TODO: implement container - Container get container => null; + Angel app; + + WingsRequestContext._(this.app, this.rawRequest, this._recv) + : container = app.container.createChild(); + + static const int DELETE = 0, + GET = 1, + HEAD = 2, + POST = 3, + PUT = 4, + CONNECT = 5, + OPTIONS = 6, + TRACE = 7, + COPY = 8, + LOCK = 9, + MKCOL = 10, + MOVE = 11, + PROPFIND = 12, + PROPPATCH = 13, + SEARCH = 14, + UNLOCK = 15, + BIND = 16, + REBIND = 17, + UNBIND = 18, + ACL = 19, + REPORT = 20, + MKACTIVITY = 21, + CHECKOUT = 22, + MERGE = 23, + MSEARCH = 24, + NOTIFY = 25, + SUBSCRIBE = 26, + UNSUBSCRIBE = 27, + PATCH = 28, + PURGE = 29, + MKCALENDAR = 30, + LINK = 31, + UNLINK = 32, + SOURCE = 33; + + static String methodToString(int method) { + switch (method) { + case DELETE: + return 'DELETE'; + case GET: + return 'GET'; + case HEAD: + return 'HEAD'; + case POST: + return 'POST'; + case PUT: + return 'PUT'; + case CONNECT: + return 'CONNECT'; + case OPTIONS: + return 'OPTIONS'; + case PATCH: + return 'PATCH'; + case PURGE: + return 'PURGE'; + default: + throw new ArgumentError('Unknown method $method.'); + } + } + + static Future from(Angel app, WingsClientSocket socket) { + var state = _ParseState.url; + var c = Completer(); + var recv = RawReceivePort(); + var rq = WingsRequestContext._(app, socket, recv); + rq._remoteAddress = socket.remoteAddress; + String lastHeader; + recv.handler = (e) { + if (state == _ParseState.url) { + rq._uri = Uri.parse(e as String); + var path = rq._uri.path.replaceAll(_straySlashes, ''); + if (path.isEmpty) path = '/'; + rq._path = path; + state = _ParseState.headerField; + } else if (state == _ParseState.headerField) { + if (e == 0) { + state = _ParseState.method; + } else { + lastHeader = e as String; + state = _ParseState.headerValue; + } + } else if (state == _ParseState.headerValue) { + if (e == 0) { + state = _ParseState.method; + } else { + if (lastHeader != null) { + if (lastHeader == 'cookie') { + rq.__cookies.add(Cookie.fromSetCookieValue(e as String)); + } else { + rq._headers.add(lastHeader, e as String); + } + lastHeader = null; + } + } + state = _ParseState.headerField; + } else if (state == _ParseState.method) { + rq._method = methodToString(e as int); + state = _ParseState.body; + c.complete(rq); + } else if (state == _ParseState.body) { + if (e == 1) { + rq._body.close(); + } else { + rq._body.add(e as List); + } + } + }; + wingsParseHttp().send([recv.sendPort, socket.fileDescriptor]); + return c.future; + } @override - // TODO: implement cookies - List get cookies => null; + Stream> get body => _body.stream; @override - // TODO: implement headers - HttpHeaders get headers => null; + List get cookies => _cookies ??= List.unmodifiable(__cookies); @override - // TODO: implement hostname - String get hostname => null; + HttpHeaders get headers => _headers; @override - // TODO: implement method - String get method => null; + String get hostname => headers.value('host'); @override - // TODO: implement originalMethod - String get originalMethod => null; + String get method => _override ??= + (headers.value('x-http-method-override')?.toUpperCase() ?? _method); @override - // TODO: implement path - String get path => null; + String get originalMethod => _method; @override - // TODO: implement rawRequest - int get rawRequest => null; + String get path => _path; @override - // TODO: implement remoteAddress - InternetAddress get remoteAddress => null; + InternetAddress get remoteAddress => _remoteAddress; @override // TODO: implement session HttpSession get session => null; @override - // TODO: implement uri - Uri get uri => null; + Uri get uri => _uri; } diff --git a/lib/src/wings_response.dart b/lib/src/wings_response.dart index 2b9b63ab..cbc00794 100644 --- a/lib/src/wings_response.dart +++ b/lib/src/wings_response.dart @@ -1,50 +1,213 @@ import 'dart:async'; - +import 'dart:convert'; import 'dart:io'; - +import 'dart:typed_data'; import 'package:angel_framework/angel_framework.dart'; +import 'package:charcode/ascii.dart'; +import 'wings_request.dart'; +import 'wings_socket.dart'; -class WingsResponseContext extends ResponseContext { +class WingsResponseContext extends ResponseContext { @override - void add(List event) { - // TODO: implement add + final WingsRequestContext correspondingRequest; + + LockableBytesBuilder _buffer; + + @override + final int rawResponse; + + bool _isDetached = false, _isClosed = false, _streamInitialized = false; + + WingsResponseContext(this.rawResponse, [this.correspondingRequest]); + + Iterable __allowedEncodings; + + Iterable get _allowedEncodings { + return __allowedEncodings ??= correspondingRequest.headers + .value('accept-encoding') + ?.split(',') + ?.map((s) => s.trim()) + ?.where((s) => s.isNotEmpty) + ?.map((str) { + // Ignore quality specifications in accept-encoding + // ex. gzip;q=0.8 + if (!str.contains(';')) return str; + return str.split(';')[0]; + }); + } + + bool _openStream() { + if (!_streamInitialized) { + // If this is the first stream added to this response, + // then add headers, status code, etc. + var outHeaders = {}; + var statusLine = + utf8.encode('HTTP/1.1 $statusCode').followedBy([$cr, $lf]); + writeToNativeSocket(rawResponse, Uint8List.fromList(statusLine.toList())); + + headers.forEach((k, v) => outHeaders[k] = v); + + if (headers.containsKey('content-length')) { + var l = int.tryParse(headers['content-length']); + if (l != null) { + outHeaders['content-length'] = l.toString(); + } + } + + if (contentType != null) + outHeaders['content-type'] = contentType.toString(); + + if (encoders.isNotEmpty && correspondingRequest != null) { + if (_allowedEncodings != null) { + for (var encodingName in _allowedEncodings) { + Converter, List> encoder; + String key = encodingName; + + if (encoders.containsKey(encodingName)) + encoder = encoders[encodingName]; + else if (encodingName == '*') { + encoder = encoders[key = encoders.keys.first]; + } + + if (encoder != null) { + outHeaders['content-encoding'] = key; + break; + } + } + } + } + + void _wh(String k, String v) { + var headerLine = + utf8.encode('$k: ${Uri.encodeComponent(v)}').followedBy([$cr, $lf]); + writeToNativeSocket( + rawResponse, Uint8List.fromList(headerLine.toList())); + } + + outHeaders.forEach(_wh); + + for (var c in cookies) { + _wh('set-cookie', c.toString()); + } + + writeToNativeSocket(rawResponse, Uint8List.fromList([$cr, $lf])); + + //_isClosed = true; + return _streamInitialized = true; + } + + return false; } @override Future addStream(Stream> stream) { - // TODO: implement addStream - return null; + if (_isClosed && isBuffered) throw ResponseContext.closed(); + _openStream(); + + Stream> output = stream; + + if (encoders.isNotEmpty && correspondingRequest != null) { + if (_allowedEncodings != null) { + for (var encodingName in _allowedEncodings) { + Converter, List> encoder; + String key = encodingName; + + if (encoders.containsKey(encodingName)) + encoder = encoders[encodingName]; + else if (encodingName == '*') { + encoder = encoders[key = encoders.keys.first]; + } + + if (encoder != null) { + output = encoders[key].bind(output); + break; + } + } + } + } + + return output.forEach((buf) { + writeToNativeSocket( + rawResponse, buf is Uint8List ? buf : Uint8List.fromList(buf)); + }); } @override - // TODO: implement buffer - BytesBuilder get buffer => null; + void add(List data) { + if (_isClosed && isBuffered) + throw ResponseContext.closed(); + else if (!isBuffered) { + if (!_isClosed) { + _openStream(); - @override - // TODO: implement correspondingRequest - RequestContext get correspondingRequest => null; + if (encoders.isNotEmpty && correspondingRequest != null) { + if (_allowedEncodings != null) { + for (var encodingName in _allowedEncodings) { + Converter, List> encoder; + String key = encodingName; - @override - FutureOr detach() { - // TODO: implement detach - return null; + if (encoders.containsKey(encodingName)) + encoder = encoders[encodingName]; + else if (encodingName == '*') { + encoder = encoders[key = encoders.keys.first]; + } + + if (encoder != null) { + data = encoders[key].convert(data); + break; + } + } + } + } + + writeToNativeSocket( + rawResponse, data is Uint8List ? data : Uint8List.fromList(data)); + } + } else + buffer.add(data); } @override - // TODO: implement isBuffered - bool get isBuffered => null; + Future close() { + if (!_isDetached) { + if (!_isClosed) { + if (!isBuffered) { + try { + _openStream(); + closeNativeSocketDescriptor(rawResponse); + } catch (_) { + // This only seems to occur on `MockHttpRequest`, but + // this try/catch prevents a crash. + } + } else { + _buffer.lock(); + } + + _isClosed = true; + } + + super.close(); + } + return new Future.value(); + } @override - // TODO: implement isOpen - bool get isOpen => null; + BytesBuilder get buffer => _buffer; @override - // TODO: implement rawResponse - get rawResponse => null; + int detach() { + _isDetached = true; + return rawResponse; + } + + @override + bool get isBuffered => _buffer != null; + + @override + bool get isOpen => !_isClosed && !_isDetached; @override void useBuffer() { - // TODO: implement useBuffer + _buffer = LockableBytesBuilder(); } - -} \ No newline at end of file +} diff --git a/lib/src/wings_socket.cc b/lib/src/wings_socket.cc index bd532b1b..35de2883 100644 --- a/lib/src/wings_socket.cc +++ b/lib/src/wings_socket.cc @@ -46,7 +46,7 @@ void WingsSocket::threadCallback(Dart_Port dest_port_id, WingsSocket *socket = nullptr; Dart_Port outPort = message->value.as_array.values[0]->value.as_send_port.id; - Dart_CObject* ptrArg = message->value.as_array.values[1]; + Dart_CObject *ptrArg = message->value.as_array.values[1]; if (ptrArg->type == Dart_CObject_kInt32) { @@ -67,9 +67,34 @@ void WingsSocket::threadCallback(Dart_Port dest_port_id, if ((sock = accept(socket->sockfd, &addr, &len)) != -1) { + char addrBuf[INET6_ADDRSTRLEN] = {0}; + + if (addr.sa_family == AF_INET6) + { + auto as6 = (sockaddr_in6*) &addr; + inet_ntop(addr.sa_family, &(as6->sin6_addr), addrBuf, len); + } + else + { + auto as4 = (sockaddr_in*) &addr; + inet_ntop(AF_INET, &(as4->sin_addr), addrBuf, len); + } + + Dart_CObject fdObj; + fdObj.type = Dart_CObject_kInt64; + fdObj.value.as_int64 = sock; + + Dart_CObject addrObj; + addrObj.type = Dart_CObject_kString; + addrObj.value.as_string = addrBuf; + + Dart_CObject *values[2] = {&fdObj, &addrObj}; + Dart_CObject obj; - obj.type = Dart_CObject_kInt64; - obj.value.as_int64 = sock; + obj.type = Dart_CObject_kArray; + obj.value.as_array.length = 2; + obj.value.as_array.values = values; + Dart_PostCObject(outPort, &obj); // Dispatch the fd to the next listener. // auto &ports = socket->sendPorts; diff --git a/lib/src/wings_socket.dart b/lib/src/wings_socket.dart index e5f29d93..8b875f43 100644 --- a/lib/src/wings_socket.dart +++ b/lib/src/wings_socket.dart @@ -32,8 +32,17 @@ SendPort wingsSocketListen(int pointer) native 'Dart_WingsSocket_listen'; void closeWingsSocket(int pointer) native 'Dart_WingsSocket_close'; -class WingsSocket extends Stream { - final StreamController _ctrl = StreamController(); +SendPort wingsParseHttp() native 'Dart_WingsSocket_parseHttp'; + +class WingsClientSocket { + final int fileDescriptor; + final InternetAddress remoteAddress; + + WingsClientSocket(this.fileDescriptor, this.remoteAddress); +} + +class WingsSocket extends Stream { + final StreamController _ctrl = StreamController(); SendPort _acceptor; final int _pointer; final RawReceivePort _recv; @@ -44,7 +53,8 @@ class WingsSocket extends Stream { _acceptor = wingsSocketListen(_pointer); _recv.handler = (h) { if (!_ctrl.isClosed) { - _ctrl.add(h as int); + _ctrl.add( + WingsClientSocket(h[0] as int, InternetAddress(h[1] as String))); _acceptor.send([_recv.sendPort, _pointer]); } }; @@ -91,8 +101,11 @@ class WingsSocket extends Stream { int get port => _port ??= getWingsServerSocketPort(_pointer); @override - StreamSubscription listen(void Function(int event) onData, - {Function onError, void Function() onDone, bool cancelOnError}) { + StreamSubscription listen( + void Function(WingsClientSocket event) onData, + {Function onError, + void Function() onDone, + bool cancelOnError}) { return _ctrl.stream .listen(onData, onError: onError, cancelOnError: cancelOnError); } From 61fdef1df97d7f180696a704c3a1b0141b5ecc6d Mon Sep 17 00:00:00 2001 From: Tobe O Date: Tue, 30 Apr 2019 13:40:11 -0400 Subject: [PATCH 16/28] Almost done --- .gitignore | 4 ++-- Makefile | 2 +- example/main.dart | 12 +++++------- example/pretty_log.dart | 35 +++++++++++++++++++++++++++++++++++ lib/src/libangel_wings.dylib | Bin 0 -> 83512 bytes lib/src/wings_driver.dart | 14 +++++++++----- lib/src/wings_request.dart | 12 +++++++++--- lib/src/wings_response.dart | 33 ++++++++++++++++----------------- lib/src/wings_socket.dart | 5 ++++- libangel_wings.dylib | Bin 0 -> 83512 bytes pubspec.yaml | 1 + 11 files changed, 82 insertions(+), 36 deletions(-) create mode 100644 example/pretty_log.dart create mode 100644 lib/src/libangel_wings.dylib create mode 100644 libangel_wings.dylib diff --git a/.gitignore b/.gitignore index 8078e48d..c40e7bad 100644 --- a/.gitignore +++ b/.gitignore @@ -21,9 +21,9 @@ doc/api/ *.js.map *.o -*.dylib +# *.dylib *.a -*.so +# *.so *.lib *.obj diff --git a/Makefile b/Makefile index 07bb2b86..6ac0c8ed 100644 --- a/Makefile +++ b/Makefile @@ -25,7 +25,7 @@ libangel_wings.dylib: lib/src/libangel_wings.dylib lib/src/libangel_wings.dylib: $(objects) %.dylib: $(objects) - $(CXX) -shared -undefined dynamic_lookup -o $@ $^ + $(CXX) -shared -Wl,-undefined -Wl,dynamic_lookup -o $@ $^ %.so: $(objects) $(CXX) -shared -o $@ $^ diff --git a/example/main.dart b/example/main.dart index b14aeae2..c5f4e13d 100644 --- a/example/main.dart +++ b/example/main.dart @@ -3,6 +3,7 @@ import 'package:angel_static/angel_static.dart'; import 'package:angel_wings/angel_wings.dart'; import 'package:file/local.dart'; import 'package:logging/logging.dart'; +import 'pretty_log.dart'; main() async { var app = Angel(); @@ -10,17 +11,14 @@ main() async { var fs = LocalFileSystem(); var vDir = CachingVirtualDirectory(app, fs, source: fs.currentDirectory, allowDirectoryListing: true); - app.logger = Logger('wings') - ..onRecord.listen((rec) { - print(rec); - if (rec.error != null) print(rec.error); - if (rec.stackTrace != null) print(rec.stackTrace); - }); + + app.logger = Logger('wings')..onRecord.listen(prettyLog); + app.mimeTypeResolver.addExtension('yaml', 'text/x-yaml'); app.get('/', (req, res) => 'WINGS!!!'); app.fallback(vDir.handleRequest); app.fallback((req, res) => throw AngelHttpException.notFound()); await wings.startServer('127.0.0.1', 3000); - print('Listening at http://localhost:3000'); + print('Listening at ${wings.uri}'); } diff --git a/example/pretty_log.dart b/example/pretty_log.dart new file mode 100644 index 00000000..079cf258 --- /dev/null +++ b/example/pretty_log.dart @@ -0,0 +1,35 @@ +import 'package:angel_http_exception/angel_http_exception.dart'; +import 'package:logging/logging.dart'; +import 'package:io/ansi.dart'; + +/// Prints the contents of a [LogRecord] with pretty colors. +void prettyLog(LogRecord record) { + var code = chooseLogColor(record.level); + + if (record.error == null) print(code.wrap(record.toString())); + + if (record.error != null) { + var err = record.error; + if (err is AngelHttpException && err.statusCode != 500) return; + print(code.wrap(record.toString() + '\n')); + print(code.wrap(err.toString())); + + if (record.stackTrace != null) { + print(code.wrap(record.stackTrace.toString())); + } + } +} + +/// Chooses a color based on the logger [level]. +AnsiCode chooseLogColor(Level level) { + if (level == Level.SHOUT) + return backgroundRed; + else if (level == Level.SEVERE) + return red; + else if (level == Level.WARNING) + return yellow; + else if (level == Level.INFO) + return cyan; + else if (level == Level.FINER || level == Level.FINEST) return lightGray; + return resetAll; +} \ No newline at end of file diff --git a/lib/src/libangel_wings.dylib b/lib/src/libangel_wings.dylib new file mode 100644 index 0000000000000000000000000000000000000000..58a946f4ae50d04f71a75153e34f0033cf7e0144 GIT binary patch literal 83512 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 z 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
  • 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 literal 0 HcmV?d00001 diff --git a/lib/src/wings_driver.dart b/lib/src/wings_driver.dart index bc4bff0e..feda34f2 100644 --- a/lib/src/wings_driver.dart +++ b/lib/src/wings_driver.dart @@ -31,14 +31,17 @@ class AngelWings extends Driver createRequestContext(WingsClientSocket request, int response) { + Future createRequestContext( + WingsClientSocket request, int response) { return WingsRequestContext.from(app, request); } @override - Future createResponseContext(WingsClientSocket request, int response, + Future createResponseContext( + WingsClientSocket request, int response, [WingsRequestContext correspondingRequest]) { - return Future.value(WingsResponseContext(request.fileDescriptor, correspondingRequest)); + return Future.value(WingsResponseContext( + app, request.fileDescriptor, correspondingRequest)); } @override @@ -68,8 +71,9 @@ class AngelWings extends Driver null; + Uri get uri { + return Uri(scheme: 'http', host: server.address.address, port: server.port); + } @override void writeStringToResponse(int response, String value) { diff --git a/lib/src/wings_request.dart b/lib/src/wings_request.dart index 6b97e8e6..83203f29 100644 --- a/lib/src/wings_request.dart +++ b/lib/src/wings_request.dart @@ -28,6 +28,11 @@ class WingsRequestContext extends RequestContext { WingsRequestContext._(this.app, this.rawRequest, this._recv) : container = app.container.createChild(); + Future close() async { + await _body.close(); + _recv.close(); + } + static const int DELETE = 0, GET = 1, HEAD = 2, @@ -106,18 +111,19 @@ class WingsRequestContext extends RequestContext { if (e == 0) { state = _ParseState.method; } else { - lastHeader = e as String; + lastHeader = e as String; //Uri.decodeFull(e as String); state = _ParseState.headerValue; } } else if (state == _ParseState.headerValue) { if (e == 0) { state = _ParseState.method; } else { + var value = e as String; //Uri.decodeFull(e as String); if (lastHeader != null) { if (lastHeader == 'cookie') { - rq.__cookies.add(Cookie.fromSetCookieValue(e as String)); + rq.__cookies.add(Cookie.fromSetCookieValue(value)); } else { - rq._headers.add(lastHeader, e as String); + rq._headers.add(lastHeader, value); } lastHeader = null; } diff --git a/lib/src/wings_response.dart b/lib/src/wings_response.dart index cbc00794..206ea4b9 100644 --- a/lib/src/wings_response.dart +++ b/lib/src/wings_response.dart @@ -8,6 +8,9 @@ import 'wings_request.dart'; import 'wings_socket.dart'; class WingsResponseContext extends ResponseContext { + @override + final Angel app; + @override final WingsRequestContext correspondingRequest; @@ -18,7 +21,7 @@ class WingsResponseContext extends ResponseContext { bool _isDetached = false, _isClosed = false, _streamInitialized = false; - WingsResponseContext(this.rawResponse, [this.correspondingRequest]); + WingsResponseContext(this.app, this.rawResponse, [this.correspondingRequest]); Iterable __allowedEncodings; @@ -78,8 +81,9 @@ class WingsResponseContext extends ResponseContext { } void _wh(String k, String v) { - var headerLine = - utf8.encode('$k: ${Uri.encodeComponent(v)}').followedBy([$cr, $lf]); + // var vv =Uri.encodeComponent(v); + var vv = v; + var headerLine = utf8.encode('$k: $vv').followedBy([$cr, $lf]); writeToNativeSocket( rawResponse, Uint8List.fromList(headerLine.toList())); } @@ -127,8 +131,9 @@ class WingsResponseContext extends ResponseContext { } return output.forEach((buf) { - writeToNativeSocket( - rawResponse, buf is Uint8List ? buf : Uint8List.fromList(buf)); + if (!_isClosed) + writeToNativeSocket( + rawResponse, buf is Uint8List ? buf : Uint8List.fromList(buf)); }); } @@ -168,27 +173,21 @@ class WingsResponseContext extends ResponseContext { } @override - Future close() { + Future close() async { if (!_isDetached) { if (!_isClosed) { + _isClosed = true; if (!isBuffered) { - try { - _openStream(); - closeNativeSocketDescriptor(rawResponse); - } catch (_) { - // This only seems to occur on `MockHttpRequest`, but - // this try/catch prevents a crash. - } + _openStream(); + closeNativeSocketDescriptor(rawResponse); } else { _buffer.lock(); } - - _isClosed = true; } - super.close(); + await correspondingRequest?.close(); + await super.close(); } - return new Future.value(); } @override diff --git a/lib/src/wings_socket.dart b/lib/src/wings_socket.dart index 8b875f43..3a9a3c38 100644 --- a/lib/src/wings_socket.dart +++ b/lib/src/wings_socket.dart @@ -44,6 +44,7 @@ class WingsClientSocket { class WingsSocket extends Stream { final StreamController _ctrl = StreamController(); SendPort _acceptor; + InternetAddress _address; final int _pointer; final RawReceivePort _recv; bool _open = true; @@ -91,13 +92,15 @@ class WingsSocket extends Stream { addr.address, port, shared, backlog, v6Only, recv.sendPort); } - return WingsSocket._(ptr, recv); + return WingsSocket._(ptr, recv).._address = addr; } catch (e) { recv.close(); rethrow; } } + InternetAddress get address => _address; + int get port => _port ??= getWingsServerSocketPort(_pointer); @override diff --git a/libangel_wings.dylib b/libangel_wings.dylib new file mode 100644 index 0000000000000000000000000000000000000000..58a946f4ae50d04f71a75153e34f0033cf7e0144 GIT binary patch literal 83512 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 z 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
  • GpgHm`$7A;@@pwZYrhfgz<#%>I2Y{o@n)8lbA_G%k9_V*aX zKL*ftIW=}i@;9up|8iDHV?PnJ3==j`j)t(|j?nbUS0|H^wLs4`8}n5RyF+Qr`@$NN zanYFcc$|&-#5``yry0b*2GDj5HReaxe8U=Z^qC=z`DgB9hE1=R4G5tqKH8q9!QA@_ z92aV_KpT#eA$+8sf9x`{^=$}iL&ilL(&KTqp*Mxw@M8w?r@-d6Euc0`n)3~7Lp#iJ z!0`GY1L}3xQlpV@!*4BEiRE#deo??UUPM17KjkI*NSsSL$5`Q~UmS4PQKQkaCh~-B zBT*D~fC%7g<$IBM77y}5f0Tx%HHcB`2RaHAzTQpu@K61~O~H^lXLK^R?HLT>`vBU$ zLv5QgJGefWVs3^W#!(&`mGEm4Tv_lW7@=(n{DAQkY?=YcP&k}8M@y+O`bbvILq;fx zg)`L8DCR@VZ~@90#BTs-D!}!rPvBQdh8f;3iRT1;Y4NdB4S96hHlvzSD-Zq#$~6INIzh6E)SgoFjNQS1!Z5Kd z+5~rDD5FOWR4v%=W1QH~-Sl{zmZuaJ2916fgZLi-wB19EzIB#S5iu9~|K9I=0b4== z^TO&vuDtN>lZ{qB&PkNv3f@7)qp>~es@;eeS=$FO75iWk&=~JBh#x_Xv5p#J3MMYn z8VD^8bxy=rLPI|hDvi(g1;vL2#hsY7wKWjM*Dg0GdSWk0!OW(r z=FhG}{$zDy`0nO$eU|Ag>U^ONhcv&}6$tjk)>*KcU*=t}_| zZij#x@O(3czW60R?aFnI$Bu=)6^GyHPvwe>E#@TPSgK{vu`dR@%=}35#s)J#%OJVI ztll5|1=tVl`Fq3%YiU+_3{LoLoC~{57%W7&a{u@)3^KfDUlEXLZ%ar#j8V=?CgF!r<>ul?!^3E~J!%FjPeqm6{zP@10X__7Y3`jo z=xF;qiapk7o6O5sC*=FZBfjFOnh16wU-&GcbMNcN$O?0Y^lnF&2v>cvR(zCEW{P0> za>4Sg#PWN@@*P3Pka%s)_@+GLM=mF zV*0MnaVyj7GguAijWOuIN;3bHWPaW>Sw@f=9Eas!J;GpdfaQl`Ky!~vF|H6y&KFEB z1!y}=Os>IZrVxa1n+M-!8j8ubpE#5KRkxW2YxxIhgop6tO?Kr(*eZ18NetrGl6dg8 zpW1*fO9@X|fkXA-IRofxU5uh!h=;lzgnNZc% zVcFw4g6~;A+MZI^hwoeV(zh+!%}>5d7OP=Zz~a3O;y(tbZR3f>?H3t!p<1!_4$)YA zTTo0A6f*$YP9uuT4T_lKw_{5w=uC3rpd`q%s=o$*3`<|t3UQc#4wSfKzR zFX$Yvs9RwR(0IRR5dRN=w$n+}%7q4b@hU)&sI6Z~)KQD1n0L0)(pL82af0d6RwjIA9PU(>HKM-ufjhRuMen?pq% zaJi2*M&YLsafqNASqB)Lp}aa`HD;M?xQ13F1{)mk6vr1(Xg_`@u^CP%{pM!@zyFM%`27XAeuL{XT()MvzZ=&DKlS^6i)#eDl6AOzKll5uea!E_{uh3K z6Rux9?)TsRUx@#0==B!x!LG-*`u)qcfiA8uaJ|ur`6#X*;+pcb-~SY@Q8*Cs0Ioh< z*Zt1#|NArGWsl!~O%E1zan<7a!sihGs^9-3T<(K@|Hgm%{qgT14%bb81HHfd{afEh zIvj;v^|9aIgO5lQAMpDh!u1Gf{C;VKjD<;H9W8EMjO23_kW1%{+HqX;pe|^!L=&r`XlPw{(H!+)9>%X zvkT9u5?_k2-$6zlkkOd!$cyV!T#w;6+6~Y8{YP*;-tPA=?hscEAYBH2D`eb$oL6;N z`VGK;fh*%bA!A(ixPF4`f1o3DJ%#5NgK!Ev06Z4ZifcBm8*x$i9k}kp^$T1(anbMh zCO7oqXL%cJpW^!;pSq4%)%r8hlQ8}L)YTvOU^Wo*o#VsgLHka@s)mlOW3?AIqeezvn&J3frP#e@Y_ge`McYcTNH!oZ3*g6QwrkC7 zXA{x2W<-DK?%!u#mAx%ng% z77b{ekv%U2hN94*jWj;!c_-)(5Fluaqu*74+NqVR|k7 zb+sOzfM{&&72`*~G+R%|?`j<8rll{rLzQt}Tk-uE&%O<~8Em|Y_1j~zAvO0~L}76# zAFtq-hC}#xh52|sln>5%P7ojCh>u}Gd}JI4A2d7m8OkE%(WRyD4&kGV`Dj<&ECb{q z8F0=A@GSeMPL$T*W{9H`%K*m|M^LL=kG!N;l8a}w^j2{uRQdzYFc(j$?gT0cmr?Rq z!eIqPAXtMe5O?^tT~~dMu@Kk0xJDnw7%Z;~0ssDm-(QG58@k@bRgK{*?Dv!t@cZ7_ zhOg~Yw_TS&d$k+pfHFBZ>Nfj0I@7lSPP_1Sf{**f@DV}T(nx@}A?X$|Z4@633{GpM zw75VQM^+}{fr=J0ELup#7eEiAcJjC2)d}+zg@6BD@~RJa^+xj{mtdmLfJ|!v6Xj2% z(<}KnMQA&yb^l1OKKSVoSHDHAPv--97Y1gXUL7V;a^5@OK0rWt8`~F#Hb!|J+FbJAwBZ_~T(XD^L)9UBrK4q`ynx z9~$^_fq!P;w+LJ@_*p3MXak=ua8uqF34F8>e^yxfQ38)O;y+io{=*FX9f6zq|0eJ; zM*JTHevX0vPT&_C_~QbfVc-vi;kv-58S!_8#V-%TZxQ$$BmF{w+YEfRz^^y(iv(^r z@UsMNrXMBnwHU$Bulr$e`fuCScn!vZ3|zP4dKdQpjcJ-9aDdK6c%Or8Y31J?_<-Z0`M zJOVgI_P29RK)(wvciIW27fk!#^?VKLkb&!VT&1|~!Ll1ZK zmEJ#Jx8N0_6j$%zlo5UNHH)kEBO2d&#mh#m7TS_QV~0Y_$P+F`WPN(;neR=z&OkE z6W76nAfJ7rdO!cZ2hC^>+4DQaIXAdJaLeXAiNzT#*Z&#(V|w2LtZgMN*C&9i@ZdeM zJ(CSrzhi%l-O_E>@f-G3nmR$uhmNDXT2Y=;l$T89-A&~+#|O$Q8c-h2m~(-JX8j-! zYeae61wu6a2$e@U&o8xVua^5JR^)m*Z8S|oHEO+zx#R)I4L{2X_q~-C?Vkp3P=wr` z!cOvN`C%@e^y)Y1MKK#Dhp%9(m1a(e?6Ye)94}5@_2CQAUOpv%8A$52uTZ$UBgwnw z9Xdt)if(&F{J!})U}ckL+o-N9A}XHPzj8_Go6L!ioRj%TrF?%zqE6dx+!f!A{QL~D zj3DNGo_GfTiVugVD4cZAM-CgFdD<>peGC@t(CUB3{2r}-8nCwf5om=oF_G16yJy?v z3IrJM(bIQpdkgid#$ko{h;pHZ2j4=F6pIh|a5L9_%1edX3()T#y>_?u8B+ZqCV+L$ z{{dD_K2o>+F5SKx0JRWp%Z0T6WSJs_fC0!&s-ZpA=7xS z_(QmxIXIejpAf_!+U(JDpYg^*etZO6&eS(NC2%x~Vwb-`7;=}iX#db0B zi9(2!Xtov~_Hn4$n)x@CZ9RpWJqpT;q_jSS&RBliu1hg)t~-I(e9*c!==u(xGjZk1 zFgglI@ps`0v!_df?SWqHRkz(3hYc>k1tTpx?fD9vggCRzy{RI z?$m%e5SpWrOphmiJ+QXFkWu>sZY(MBx)XE|3YP0F?3{~s3bG@;q$^NYly+U5sU9t6W-T`xBMOW8s&%0`$$A^`ITZrs|j)?XzXbWNltW3LRdq!P1 z9pSJtEd=y*Xx{h+yJe?;zpJ(v`hFU-CySiYRfw?$N39CAOh^V$w~s8use3f+NZ&t^ z6q$w}SEg1$Ka|qX1*OqyebQjXkLKb#lWCv>BkHy!*9$R)+6RUFWmO_KMlv?Wmm|nU zABEi6k{ni+O>)U0j8zef9mo^X|^ZSb-bF2xkNr(}nb$CA7zS5xUo%^12Nr z-;ay%PC&Zo`E@{J{15VP^i|`GF6xAy@IB}^dcyg@+I}+vJ)r}mj~v1a z=`UnGka#6uMjzhcrEl=N`tg25M;))g(&Axw`qrosQP_Ak*JGuxGbaH|L`rY&_knrW zo+H}(u6JScuq{`OCC6xtk?0c%1cH|w#lB0j*|B&i-H#r}5y#wC3Tz&QZ!7%|BqfMr zTdCVbu&uPqM6j*2)kLtZ^h*=Lw$j5Uf^DVmn+Ud*?llo?D^;5aww1`g5QSn}=@t{g zwi1m3A_m(^qy_@Pwi32_B!X?F3rqytN`dWCY%9U&$z&Is!h)FaE-3jdzr;;Yyv2-V zPvk)(;+Ucg@DVP`03Q%ofRCwWNjMXSUt1zRXeW#L_?H*A1J;#0u_0B%G>;-?nkjZ8 zZX!0Y--e+=@!cDmH@v>{k!A>$C{#BDB!gBBNM@QT8FE{Fr=U1jZU1ob!DGSEBr^&6 zt>WvW+nYwY{bBsROtH9z!WH&f1Bw@3vlN3yKlC%u;Aqx>;-R|)s)g|Ag=TeE*E^hxvYl?+V24pA^OS7`_kVdmP_K@_jVl$M8LY@8kGBp6{pe zJ(2Hc;;txU&=lVr^hCJt6?&psk8dwM(FDcUPEUpOM8>q}UV3_ho*tm5pV8Cf^t6ed zeos#u=;>8@s;8%Zdb*RIsHgbK>FHv6BBDhZ^mHpdEup6@dRk3Sc6#Dpf24Qfi^$#L z59SoTNKY41*gN!OrKdQuPN&h+dGthDSwz1X;=_f%G|#ujA~q%4Kgj~vShR-X-h$Q@ z?V+dF>FI5H!eJkO5p7oay69;gJ?)^U$LQ%PdU}bTenn5iF`W6}+xv?q($fR5pou7IAF(bLWJR7X#l^z?mtT0l<^)6>=T^ecLrK~K-n z6TL7~^a4GdLr=Z*G@hQ`qoSioRcey?{qqvm zE33=PQ6*=AmC`9nc2!=L(^gRdZVMo?tMbb#oXUc{;u55&DzoOJ#44w?l4UqenU_~u zT2^JPa8_43t$76n70${^YhhW1nIMppXBySWQB_t}atTFG718`GlTOQ5>^V7^j?9#- zY+IJYmX(#UXtGE)#X4t>)t+f{ELy1Kmz9<}^Q(%>N|o}8vYORXg97k~wyT<2;w)WO zRiso^J^y^`xw)mwOUqW3T8q$<&I(5*iT_*~ zmt9m=T~bhbL6sGXhZGggY3C~WMb)LtodwErr?Y%&UPbn8{tq$$>mtVZn0ypn3?z}V`Fl7WFs%L-Nx z45jj_D?`JlSg)oET@AUBsIO7xp^1u1tDQ>rs^Y5rA~cFMv!bl3EWfOzQo&O`w3u27 zEj^F*y+BFH&v%ximna!!rBhR?tBT6djRw9n}SF)U>+6I;h z%`46;G4b^3lB(jV)Y+;lm6Q?^`)X^JQ*=coHQkz9iNZKld5P0Huc)jTTq)^gDXER}4K;5V^xSpkXD^VIVfwr-xIhR4(vYdI9 zWu=u$Dpazf6am@Jij~d^tFZ6XIH@b3UevT;mswISw#4T0ER)a27K!~$yRT*r>U@F5_-dRymTIMLtTj5kD%h7I%6@lpY)|9NQ zl$#vcsW;mubK1(P3dEFEII8lNPEmrg7syeXdNBtJdnv{R2P}{>5w_aNN)fPYkW75Xgf)G``(%kyZ2 z6+BssOR0J^%0fReTFkel*>Y^kb+#PEZo@B$W-iLkQ8IIL6dF@)^Kz6$nK`M8GP0GN ztdw~+gl68Pq%E4aP)T1nZ&8|(zUW4ql9{zAb3tkbGUFjLC1;*p$+o3r0pwFKtt>ugH;R7odeQBLZDo0RO_ zx!LowQs>%0IPfe=%*^E+3+JVz*)ry*WGQK>84HO40@;glv%tL^f-9?GRw^qQoj2F9 z0?*JT$5Q9A;!*|n1wKM(pfz9mN!CK{mPSr$TA3b?gQW@{zKrC-VaLeF16x==4m*0j zN=JSfbx`;&l0{KwAbha=G&&l2=y|YohTOu-2@fAEzZh?gk_0wbTJ~HVq6S8e;IO0T z3+GVeG(schP0hG5B`tNn!?tLFBReMrU4*)S-U=%l7i(UXHLuiK9q80m7bu zILi4>_zVTsyh2!HtHFH%o(zxjDl5BlR@nl~k5LiulTEaWOQAf)1y;7eFgs*{LlRPL zbF)L!8I4m0-;-E0h^E_e?2G1yrd`29c}TL{tTZzXJqMGplff~VDk_8b5FE<}!iXgg zCp0!C$8P2=&x6YxocD%YTNdnhPF8Bhb!IAziEw1lmgJ-br(Teia$UMDBfv``1{fOM zI91vLhi!2t>>+&Ev<24uqP&Vc3^^5;6VXtQ;U<`t)PbBu3vB^`2lBuMOHB^Wg!axs z`3{(`>w>Bkk_}BAG(iZalAB@hM~V|nC`d^M&u%m*`{_L9nGy?SfMyUp-3u<<%#DE} zCnZRRC=jZH@j8OQQ^93cMoOCOUNpz4CSL;@9>j97;eL<`3gx2~#lS=)^Q){Ypp(|T z%1S4kWcU(620Ak(7yU?1IjqH%VjvVPh1p2PqVHrV+=<03N(_r-`T5lqdQV&gurX@z@5Y{~Sj~-6J)A?BNP)6f6PlN}h z{a>u!@E<+U+RY`D`EwwjiSVGbD-62yKxyY-X+v=Vq4ggA`}MC9G1L}>rb&nMaRSr8y+`8W*a$b zbo>}~a2m&m|F=}~7=?U-&y33otO-v_ntV1c>YS5t5q>c)*_Q~t!J=3!Nr>sjxrqA^ zFdx^C-o_1og!fKVl+4c+g}#|_H$ki$Zw5@p#=$Fq%?ps|u%cLR0ByjQY(-fNn0%w6 z)B|=eMi?L)2qjS|VJ?tov=W^#TCps_v5aO_i7H3=ji)J=MyyabPE;((=POG2`HH0- zb_d^6i0Zxsw7_ULUZu#Ktyo&{?ndt%q)SG+WQ5N}x_O{y13bo`6)WvS`4hiXsVL)k z+Xk|f@O%lAB)m$(`4VPGxLCra5|&7~Qo=e3*Gsrj!bc?(-`-S|-%9)$3HM0YE#Y4! zd{e@L1G07!HcOc-xk!YMl;PtgJV(OG5>ngJU-z{Fk`B;c`6K~n$aDe=+)HKn)e_c7 zxIn^832&D0UJ37$@Bs;%CH$3yPf55#!Y&D4mhg26-;%IT!p|g(c}MU#M#4l1&y_HF zhO9#}b&0@V+(+VBmGBe^6D9nXgcBv4BH?riuaNL+2~#AzPQo+^b0u_0SSaBN2`eOA zEn%I6^%Ck5J|N*wB>WEvpOkQmgwIO&2MJ%2@HGkdOZc{g?@IWwgoh=J`B=#33<9&>14R<6oQfy9Yc(hU%Ct_|K5X0dGC7COgx|XQseq$ZU!r`T> zRw!X%j%779j&f{Alwq$K`{0h1NlINi4odQ$rEwfMpc5|n%N_Ye%N@(A^C}9I#^i$4 zI1++|xutw23uhSW@J%Tr56PUXwJ^KtQio&u;3z@@}i*g)Fo%Ji0aXCc0DzDsulM{~o;)?w0lDrBBcAs(f2ut+Yj_H>n zK{=X529k8LvS&gl; z6+QZnmO3~fH(OJ%_>WV;%_5|XX_nfjI&U?*B9IY%{Wy4uXoWQ`|Du{XmaX2(oFdy5w zDfxU}haM?K-@p`EPN)-(fXWp87BYdkrLbHD4sZ-jqKtJ>!g5QSG$YC35LONbqtZ!d z09KThrlu{OZnIHb74asI_X!gZ%LX!=BpKN8={O}>oG&8h&9G%I%%=p+W&-Nz(`SG> z5{YfS)O@lL(`lp7fisK6Rh6mvwlI5&02{h)acOZCkz7GEH=h8SJVJq1O9-vYQBRz9 zG9r6KWOmh+*k4>xTnbg;TK7fqup)L39TipFSpA&XkzbSNFwVi^6lhRu)5!y;9otGt~oe*)t6Id5v#igK0r)?>|+YBkKDl4au9y0`oj#(VN6e155 zBY~ID6)YG^-^ZwPMSeNkk$#DSaf+>rL1LU+U|}~9f(SbQTUO5TjU2@1fhqYWMyo3F zS1>Qlgo@MAjo6@ZD6vX39k(lS@aXMGg)S+R95*W$@?Nd7F+N)9zDQAGrYlt$$|q4X zl)nzYbOzhzpmAQvBXF=f6GIcH3ANFc%Hh#U>~!VVQL~i#k-}eC0I`Y_;Dcq#T4=Zy zGlPnxQ=YbB<)KlRGvmUl%%#JZgVBlXp(xFxu3!rGpwr4VnbR*76)=k@?|AuOY=&E z_|tI4-KP9iRbu8T@5NkET$*2zEz=>s#w&1Zr(4}@Lh05w8#RmM9x08(BDZ;{Zz;Ue z8Ol?mAfUk4b5K$Z=$Drb8tWf%`Ji$Cfp5WeW0n>LZo_)iV0p|Y8K}MwjAEJ2g*Ah> z_Z1AGpEmdP{I?SwT;w)QJbv(ZJ+o)9B;Y(!lKF5vWjm4fM2ojS>^n ztXq^ArTK?+@55b*;?ZL;5hD z@s-Nw@$krgHpa;(ij}o0zhqEc=_rK1QKEFI$}rre-r9J04Eh4zRe=Ac{BaC8`w`9%M2TaprSy)D z!&z3Sbc~@x^_-{^iRvDTCc!*R_x4BV-tt?@M(2bp1qYvx!Ps6S&sp-sS9t|o^!+!b z<5SX`)Zo+3k0P@9DI(Rlh3<7*DIs1o;#1GhsB|QGCs%`FoAFsj@cMgBxrgqJeD7V4 z(2MFUjnT>0^4{2__UMEqbum_e?qSK@qpWpNb;IgndMyc=%~35e<(Vy(mJvxDMKPJ> z&6doB%(_^s`Ilg=vsg2&3D$(9-Wc=0vB%Ols>RZ0$?O~7Zb>RHPil`(D+QF_z^{@xoi(Aq3dR*> z2?lBU+it{5{C(hKpgT*Zi}*NI;KqLbWQixp^9)x5r}7%*xdvV0e1-)iFxWD^qzpmN znjr8@i5E(|T;fkiyiwvGNW4Yjv&M@2-4ef7;(Zc-Ug8OhM0ux;ljTc%iNrG{{tJnh zOWZ5*I*DI`qj)5bW{H0A^2{fETqd=vd`lQ^AoqQB=PPUoHI?{$gO zxhML2PvUg`iT^Eua|hA#OoxUDa+d?@g-}8ny8;}0Le37H zPl|6HLLGiy7-*Rd3{amg1jf!tmd`|IY#Wxf`srV;~ zf35g$%yHK7gZ_6e=ATangc_F>`K;piDSlS*zZKuSshDrf@LsmPrfQ#2?fVp;=l)s! z`_=dj)qY&HpH}=+#lKSg7v|aaepmb-#o>pg$;|p+rT8_9-=O%o;x{Y4L-AR~=M=wN z@p~2TD!#6GPw}IQ)4$+w-|zi6h8BS?ummgv2Y`dX1HcNf3Ooor1grrM1M9#c;1S?a z;4$EFfE?;i06pMI;3;4qa2v1z90r~NjsSDO?Z6$toxoAxdEf=$MS#8=UjmK+$AOoD zSAo}n*MT>Hlfaw6Tfp1EJHRR6UEn?7ec%J&L*O*<5pV{;W9!rW+x05O>YX&}gzRBBYz=*vg_MThb+ z{e=t~Q(FzALG|@88da&+RjXTlhgAz-G;A2D4dX^o{xRCh_^t{)fN)$f^1npsiear> z*ska?)klb~8q!DDc7~Mb?bNzUE=}{h^&3P0&+>B z5^Y_N76^lKY|YGZ?|M3s^5TJF&HdO?K-UkBlTAAQnPYbR7yFwK(tyb!WM!?el0{yZ=+(WFiDz)5G~Sb(NuSEB-TPxCjAzo zRsiPUs;B-+JePn=3@99$_N*sa+?Y<7DZ7aZiseJ1VllByoEuDwwi1q!pCo(b8%eSz z$3c?Ctal?%z?*YDBhs17BK*>IiAbbq58OwyPo8H^H_#8^?X$Zk zu2081YkPL=-xJ5?3W~37;H0JZSR+duBuf$`%S4bYjUZVjOR|t_vHlD9*H#%!|_gc_ynjU4pc`xVjW4KI+79VmuCn4rxmcCR)Bii0qbd3)RXTZ?M32-7fSr(Nl7n1d(w_@tK!84+`co#{d5=BqjZ_> iGutIWL*B0^S@3<^==z@gU@{?KP-6I=msoL$qJIIT^KG*L literal 0 HcmV?d00001 diff --git a/pubspec.yaml b/pubspec.yaml index e24d2504..1d825c84 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -8,4 +8,5 @@ dependencies: dev_dependencies: angel_static: ^2.0.0 build_runner: ^1.0.0 + io: ^0.3.2 pedantic: ^1.0.0 \ No newline at end of file From 8a1b95c3529df8e394bc40b8cb1bd62344c3609f Mon Sep 17 00:00:00 2001 From: Tobe O Date: Tue, 30 Apr 2019 21:33:46 -0400 Subject: [PATCH 17/28] Fix addresses, get dill, etc. --- Makefile | 7 +++ example/main.dill | Bin 0 -> 2011096 bytes lib/src/angel_wings.cc | 2 + lib/src/angel_wings.h | 2 + lib/src/bind.cc | 60 +++++++++++------- lib/src/libangel_wings.dylib | Bin 83512 -> 90608 bytes lib/src/util.cc | 117 ++++++++++++++++++++++++++++++++++- lib/src/wings_socket.cc | 17 ++++- lib/src/wings_socket.dart | 8 ++- lib/src/wings_socket.h | 5 +- libangel_wings.dylib | Bin 83512 -> 90608 bytes 11 files changed, 185 insertions(+), 33 deletions(-) create mode 100644 example/main.dill diff --git a/Makefile b/Makefile index 6ac0c8ed..de7fe6ea 100644 --- a/Makefile +++ b/Makefile @@ -14,6 +14,13 @@ clean: find . -type f -name '*.obj' -delete find . -type f -name '*.so' -delete find . -type f -name '*.dylib' -delete + find . -type f -name '*.dill' -delete + +%-run: % example/main.dart + dart example/main.dill + +example/main.dill: ./**/*.dart + dart --snapshot="$@" example/main.dart mac: libangel_wings.dylib diff --git a/example/main.dill b/example/main.dill new file mode 100644 index 0000000000000000000000000000000000000000..d3327efd2acab6d6f4ced1a58f350e07ac088901 GIT binary patch literal 2011096 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