├── .travis.yml ├── .gitignore ├── AUTHORS ├── pubspec.yaml ├── lib ├── sync_socket.dart └── src │ ├── line_decoder.dart │ ├── socket.dart │ ├── sync_socket_extension.cc │ └── http_client.dart ├── tool ├── travis.sh └── build.sh ├── test ├── http_client_test.dart └── sync_socket_test.dart ├── README.md ├── CONTRIBUTING.md └── LICENSE /.travis.yml: -------------------------------------------------------------------------------- 1 | language: dart 2 | sudo: false 3 | 4 | dart: 5 | - dev 6 | - stable 7 | 8 | script: ./tool/travis.sh 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | packages 3 | pubspec.lock 4 | .project 5 | .buildlog 6 | *.o 7 | *.so 8 | .idea/ 9 | .pub/ 10 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # Below is a list of people and organizations that have contributed 2 | # to the Dart project. Names should be added to the list like so: 3 | # 4 | # Name/Organization 5 | 6 | Google Inc. 7 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: sync_socket 2 | version: 1.0.2+1 3 | author: Marc Fisher II 4 | description: Synchronous Socket and HTTP Client. 5 | homepage: https://github.com/google/dart-sync-socket 6 | environment: 7 | sdk: '>=1.9.0 <2.0.0' 8 | dev_dependencies: 9 | test: '^0.12.0' 10 | -------------------------------------------------------------------------------- /lib/sync_socket.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | library sync.socket; 16 | 17 | import 'dart:convert'; 18 | import 'dart:io' 19 | show BytesBuilder, ContentType, HttpException, HttpHeaders; 20 | 21 | import 'dart-ext:sync_socket_extension'; 22 | 23 | part 'src/http_client.dart'; 24 | part 'src/line_decoder.dart'; 25 | part 'src/socket.dart'; 26 | -------------------------------------------------------------------------------- /tool/travis.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2015 Google Inc. All Rights Reserved. 4 | 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # Fast fail the script on failures. 18 | set -e 19 | 20 | # Build the native extension 21 | tool/build.sh 22 | 23 | # Verify that the libraries are error free. 24 | grep -Rl --include "*.dart" --exclude-dir="packages" '^library .*;$' lib/ test/ | \ 25 | xargs dartanalyzer --fatal-warnings 26 | 27 | # Run tests. 28 | pub run test -p vm -r expanded 29 | -------------------------------------------------------------------------------- /test/http_client_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | @TestOn('vm') 16 | library sync_socket.http_client_test; 17 | 18 | import 'dart:io' as io; 19 | import 'dart:isolate'; 20 | 21 | import 'package:sync_socket/sync_socket.dart'; 22 | import 'package:test/test.dart'; 23 | 24 | void main() { 25 | int port; 26 | 27 | group('HttpClientSync', () { 28 | setUp(() { 29 | var response = new ReceivePort(); 30 | Isolate.spawn(startSimpleServer, response.sendPort); 31 | return response.first.then((_p) => port = _p); 32 | }); 33 | 34 | test('simple get', () { 35 | HttpClientSync client = new HttpClientSync(); 36 | var request = client.getUrl(new Uri.http('localhost:$port', '/')); 37 | var response = request.close(); 38 | 39 | expect(response.statusCode, io.HttpStatus.NO_CONTENT); 40 | expect(response.body, ''); 41 | }); 42 | }); 43 | } 44 | 45 | void startSimpleServer(SendPort send) { 46 | io.HttpServer 47 | .bind(io.InternetAddress.ANY_IP_V4, 0) 48 | .then((io.HttpServer server) { 49 | server.listen((io.HttpRequest request) { 50 | request.response.statusCode = io.HttpStatus.NO_CONTENT; 51 | request.response.close(); 52 | }); 53 | send.send(server.port); 54 | }); 55 | } 56 | -------------------------------------------------------------------------------- /test/sync_socket_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | @TestOn('vm') 16 | library sync_socket.sync_socket_test; 17 | 18 | import 'dart:io' as io; 19 | import 'dart:isolate'; 20 | 21 | import 'package:sync_socket/sync_socket.dart'; 22 | import 'package:test/test.dart'; 23 | 24 | void main() { 25 | int port; 26 | 27 | group('SocketSync', () { 28 | // start echo server 29 | setUp(() { 30 | var response = new ReceivePort(); 31 | Isolate.spawn(startSimpleServer, response.sendPort); 32 | return response.first.then((_p) => port = _p); 33 | }); 34 | 35 | test('simple connect/write/read', () { 36 | var socket = new SocketSync('localhost', port); 37 | socket.writeAsString('close'); 38 | expect(socket.readAsString(), 'close'); 39 | socket.close(); 40 | }); 41 | }); 42 | } 43 | 44 | void startSimpleServer(SendPort send) { 45 | io.ServerSocket.bind(io.InternetAddress.ANY_IP_V4, 0).then((server) { 46 | server.listen((socket) { 47 | socket.listen((data) { 48 | var str = new String.fromCharCodes(data); 49 | socket.write(str); 50 | if (str.endsWith('close')) { 51 | socket.close(); 52 | } 53 | }); 54 | }); 55 | send.send(server.port); 56 | }); 57 | } 58 | -------------------------------------------------------------------------------- /lib/src/line_decoder.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | part of sync.socket; 16 | 17 | // '\n' character 18 | const int _LINE_TERMINATOR = 10; 19 | 20 | typedef void _LineDecoderCallback( 21 | String line, int bytesRead, _LineDecoder decoder); 22 | 23 | class _LineDecoder { 24 | BytesBuilder _unprocessedBytes = new BytesBuilder(); 25 | 26 | int expectedByteCount = -1; 27 | 28 | final _LineDecoderCallback _callback; 29 | 30 | _LineDecoder.withCallback(this._callback); 31 | 32 | void add(List chunk) { 33 | while (chunk.isNotEmpty) { 34 | int splitIndex = -1; 35 | 36 | if (expectedByteCount > 0) { 37 | splitIndex = expectedByteCount - _unprocessedBytes.length; 38 | } else { 39 | splitIndex = chunk.indexOf(_LINE_TERMINATOR) + 1; 40 | } 41 | 42 | if (splitIndex > 0 && splitIndex <= chunk.length) { 43 | _unprocessedBytes.add(chunk.sublist(0, splitIndex)); 44 | chunk = chunk.sublist(splitIndex); 45 | expectedByteCount = -1; 46 | _process(_unprocessedBytes.takeBytes()); 47 | } else { 48 | _unprocessedBytes.add(chunk); 49 | chunk = []; 50 | } 51 | } 52 | } 53 | 54 | void _process(List line) => 55 | _callback(UTF8.decoder.convert(line), line.length, this); 56 | 57 | int get bufferedBytes => _unprocessedBytes.length; 58 | 59 | void close() => _process(_unprocessedBytes.takeBytes()); 60 | } 61 | -------------------------------------------------------------------------------- /tool/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2015 Google Inc. All Rights Reserved. 4 | 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -e 18 | 19 | cd $(dirname $0) 20 | SCRIPT_DIR=$(pwd) 21 | DART_BIN=$(which dart) 22 | DART_SDK_DIR=$(dirname $DART_BIN)/.. 23 | PLATFORM="$(uname -s)" 24 | DART_VERSION=$(dart --version 2>&1) 25 | case "$DART_VERSION" in 26 | (*32*) 27 | MACOS_ARCH="i386" 28 | LINUX_ARCH="32" 29 | ;; 30 | (*64*) 31 | MACOS_ARCH="x86_64" 32 | LINUX_ARCH="64" 33 | ;; 34 | (*) 35 | echo Unsupported dart architecture $DART_VERSION. Exiting ... >&2 36 | exit 3 37 | ;; 38 | esac 39 | 40 | # see https://www.dartlang.org/articles/native-extensions-for-standalone-dart-vm/ 41 | cd $SCRIPT_DIR/.. 42 | pub install 43 | cd lib/src 44 | echo Building dart-sync-socket for platform $PLATFORM/$MACOS_ARCH 45 | case "$PLATFORM" in 46 | (Darwin) 47 | g++ -fPIC -I $DART_SDK_DIR/include -c sync_socket_extension.cc -arch $MACOS_ARCH 48 | gcc -shared -Wl,-install_name,libsync_socket_extension.dylib,-undefined,dynamic_lookup,-arch,$MACOS_ARCH -o \ 49 | ../libsync_socket_extension.dylib sync_socket_extension.o 50 | ;; 51 | (Linux) 52 | g++ -fPIC -I $DART_SDK_DIR/include -c sync_socket_extension.cc -m$LINUX_ARCH 53 | gcc -shared -Wl,-soname,libsync_socket_extension.so -o \ 54 | ../libsync_socket_extension.so sync_socket_extension.o 55 | ;; 56 | (*) 57 | echo Unsupported platform $PLATFORM. Exiting ... >&2 58 | exit 3 59 | ;; 60 | esac 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Dart Sync Socket 2 | ================ 3 | 4 | [![Build Status](https://travis-ci.org/google/dart-sync-socket.svg?branch=master)](https://travis-ci.org/google/dart-sync-socket) 5 | [![pub package](https://img.shields.io/pub/v/sync_socket.svg)](https://pub.dartlang.org/packages/sync_socket) 6 | 7 | A Dart VM Native Extension and supporting Dart libraries that provide 8 | synchronous socket and HTTP client support. 9 | 10 | Installing 11 | ---------- 12 | 13 | Add the following to your pubspec.yaml: 14 | ```YAML 15 | sync_socket: '^1.0.1' 16 | ``` 17 | 18 | Then run 'pub get'. 19 | 20 | After getting the package with pub, you will need to build the native extension itself. 21 | 22 | To build the shared library on Mac OSX or Linux, run the 'tool/build.sh' script. 23 | 24 | To build the DLL on Windows (32 bits): 25 | - Create a new project of type Win32/Win32 project in Visual Studio 2010 Express. 26 | - Give the project the name sync_socket. 27 | - On the next screen of the wizard, change the application type to DLL and select “Empty project”, then choose Finish. 28 | - Add the "sync_socket_extension.cc" file to the source files folder in the project. 29 | - Change the following settings in the project’s properties: 30 | - Configuration properties / Linker / Enable Incremental Linking: Set to NO. 31 | - Configuration properties / Linker / Input / Additional dependencies: Add dart-sdk\bin\dart.lib, from the downloaded Dart SDK. 32 | - Configuration properties / Linker / Input / Additional dependencies: Add Ws2_32.lib.lib. This is the Winsock library. 33 | - Configuration properties / C/C++ / General / Additional Include Directories: Add the path to the directory containing dart_api.h, which is dart-sdk/include in the downloaded Dart SDK. 34 | - Configuration properties / C/C++ / Preprocessor / Preprocessor Definitions: Add DART_SHARED_LIB. This is just to export the _init function from the DLL, since it has been declared as DART_EXPORT. 35 | - Build the project with "Release" target, and copy the DLL to the directory "lib". 36 | 37 | Testing 38 | ------- 39 | 40 | Follow the instructions above for building the shared library then run tests 41 | as normal. 42 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Want to contribute? Great! First, read this page (including the small print at 2 | the end). 3 | 4 | ### When you file a bug 5 | 6 | Please include the following information. 7 | 8 | * The version of Dart on your system. 9 | You can do this by running `dart --version`. 10 | * The operating system you are running. 11 | * The version of the `sync_socket` package you are using. 12 | You can get this by looking at the `pubspec.lock` file. 13 | 14 | ```yaml 15 | test: 16 | description: sync_socket 17 | source: hosted 18 | version: "X.Y.Z" 19 | ``` 20 | 21 | ### Before you contribute 22 | Before we can use your code, you must sign the 23 | [Google Individual Contributor License Agreement](https://cla.developers.google.com/about/google-individual) 24 | (CLA), which you can do online. The CLA is necessary mainly because you own the 25 | copyright to your changes, even after your contribution becomes part of our 26 | codebase, so we need your permission to use and distribute your code. We also 27 | need to be sure of various other things—for instance that you'll tell us if you 28 | know that your code infringes on other people's patents. You don't have to sign 29 | the CLA until after you've submitted your code for review and a member has 30 | approved it, but you must do it before we can put your code into our codebase. 31 | 32 | Before you start working on a larger contribution, you should get in touch with 33 | us first through the issue tracker with your idea so that we can help out and 34 | possibly guide you. Coordinating up front makes it much easier to avoid 35 | frustration later on. 36 | 37 | ### Code reviews 38 | All submissions, including submissions by project members, require review. 39 | 40 | ### File headers 41 | All files in the project must start with the following header. 42 | 43 | // Copyright 2015 Google Inc. All Rights Reserved. 44 | // 45 | // Licensed under the Apache License, Version 2.0 (the "License"); 46 | // you may not use this file except in compliance with the License. 47 | // You may obtain a copy of the License at 48 | // 49 | // http://www.apache.org/licenses/LICENSE-2.0 50 | // 51 | // Unless required by applicable law or agreed to in writing, software 52 | // distributed under the License is distributed on an "AS IS" BASIS, 53 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 54 | // See the License for the specific language governing permissions and 55 | // limitations under the License. 56 | 57 | ### The small print 58 | Contributions made by corporations are covered by a different agreement than the 59 | one above, the 60 | [Software Grant and Corporate Contributor License Agreement](https://developers.google.com/open-source/cla/corporate). 61 | -------------------------------------------------------------------------------- /lib/src/socket.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | part of sync.socket; 16 | 17 | // functions to interface with native library 18 | int _connect(String host, String service) native 'connect'; 19 | void _close(int sockfd) native 'close'; 20 | void _write(int sockfd, List bytes) native 'write'; 21 | List _read(int sockfd, int length) native 'read'; 22 | 23 | /** 24 | * A simple synchronous socket. 25 | */ 26 | class SocketSync { 27 | static const int DEFAULT_CHUNK_SIZE = 4096; 28 | 29 | int _sockfd; 30 | bool _open; 31 | 32 | /** 33 | * Creates a new socket connected to [host]:[port]. 34 | */ 35 | SocketSync(String host, int port) { 36 | _sockfd = _connect(host, port.toString()); 37 | _open = true; 38 | } 39 | 40 | /** 41 | * Writes [bytes] to the socket. 42 | */ 43 | void writeAsBytes(List bytes) { 44 | _checkOpen(); 45 | _write(_sockfd, bytes); 46 | } 47 | 48 | /** 49 | * Writes [obj].toString() to socket encoded with [encoding]. 50 | */ 51 | void writeAsString(Object obj, {Encoding encoding: UTF8}) { 52 | writeAsBytes(encoding.encode(obj.toString())); 53 | } 54 | 55 | /** 56 | * If [all] is true, then reads all remaining data on socket and closes it. 57 | * Otherwise reads up to [chunkSize] bytes. 58 | */ 59 | List readAsBytes({bool all: true, chunkSize: DEFAULT_CHUNK_SIZE}) { 60 | _checkOpen(); 61 | if (all) { 62 | var data = new BytesBuilder(); 63 | var newBytes; 64 | while ((newBytes = _read(_sockfd, chunkSize)).length > 0) { 65 | data.add(newBytes); 66 | } 67 | close(); 68 | return data.takeBytes(); 69 | } else { 70 | return _read(_sockfd, chunkSize); 71 | } 72 | } 73 | 74 | /** 75 | * Reads all remaining daata on socket and closes it, using [encoding] to 76 | * transform data into a [String]. 77 | */ 78 | String readAsString({Encoding encoding: UTF8}) => 79 | encoding.decode(readAsBytes()); 80 | 81 | void close() { 82 | if (_open) { 83 | _close(_sockfd); 84 | _open = false; 85 | } 86 | } 87 | 88 | void _checkOpen() { 89 | if (!_open) { 90 | throw new StateError('socket has been closed'); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /lib/src/sync_socket_extension.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | #ifdef WIN32 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #pragma comment(lib, "Ws2_32.lib") 23 | #define write(fd, buf, len) send(fd, buf, len, 0) 24 | #define read(fd, buf, len) recv(fd, (char*)buf, len, 0) 25 | #define close closesocket 26 | #else 27 | #include 28 | #include 29 | #include 30 | #endif 31 | #include 32 | #include 33 | #include 34 | 35 | #include "dart_api.h" 36 | 37 | Dart_NativeFunction ResolveName(Dart_Handle name, int argc, 38 | bool* auto_setup_scope); 39 | Dart_Handle NewDartExceptionWithMessage(const char* library_url, 40 | const char* exception_name, 41 | const char* message); 42 | 43 | DART_EXPORT Dart_Handle sync_socket_extension_Init(Dart_Handle parent_library) { 44 | if (Dart_IsError(parent_library)) return parent_library; 45 | 46 | Dart_Handle result_code = 47 | Dart_SetNativeResolver(parent_library, ResolveName, NULL); 48 | if (Dart_IsError(result_code)) return result_code; 49 | 50 | return Dart_Null(); 51 | } 52 | 53 | void sync_connect(Dart_NativeArguments args) { 54 | const char *hostname, *port; // args[0] args[1] 55 | int sockfd; // return 56 | 57 | struct addrinfo *addrs, *ap; 58 | struct addrinfo hints; 59 | Dart_Handle handle; 60 | 61 | handle = Dart_StringToCString(Dart_GetNativeArgument(args, 0), &hostname); 62 | if (Dart_IsError(handle)) Dart_PropagateError(handle); 63 | 64 | handle = Dart_StringToCString(Dart_GetNativeArgument(args, 1), &port); 65 | if (Dart_IsError(handle)) Dart_PropagateError(handle); 66 | 67 | memset(&hints, 0, sizeof(struct addrinfo)); 68 | hints.ai_family = AF_UNSPEC; 69 | hints.ai_socktype = SOCK_STREAM; 70 | hints.ai_flags = 0; 71 | hints.ai_protocol = 0; 72 | 73 | if (getaddrinfo(hostname, port, &hints, &addrs) != 0) { 74 | Dart_Handle error = NewDartExceptionWithMessage( 75 | "dart:io", "SocketException", "Unable to resolve host"); 76 | if (Dart_IsError(error)) Dart_PropagateError(error); 77 | Dart_ThrowException(error); 78 | } 79 | for (ap = addrs; ap != NULL; ap = ap->ai_next) { 80 | sockfd = socket(ap->ai_family, ap->ai_socktype, ap->ai_protocol); 81 | 82 | #ifdef WIN32 83 | if (sockfd == INVALID_SOCKET) { 84 | #else 85 | if (sockfd < 0) { 86 | #endif 87 | continue; 88 | } 89 | if (connect(sockfd, ap->ai_addr, ap->ai_addrlen) != -1) { 90 | break; 91 | } 92 | close(sockfd); 93 | #ifdef WIN32 94 | sockfd = INVALID_SOCKET; 95 | #else 96 | sockfd = -1; 97 | #endif 98 | } 99 | 100 | freeaddrinfo(addrs); 101 | 102 | #ifdef WIN32 103 | if (sockfd == INVALID_SOCKET) { 104 | #else 105 | if (sockfd < 0) { 106 | #endif 107 | Dart_Handle error = NewDartExceptionWithMessage( 108 | "dart:io", "SocketException", "Unable to connect to host"); 109 | if (Dart_IsError(error)) Dart_PropagateError(error); 110 | Dart_ThrowException(error); 111 | } 112 | 113 | Dart_Handle retval = Dart_NewInteger((int64_t)sockfd); 114 | 115 | if (Dart_IsError(retval)) Dart_PropagateError(retval); 116 | 117 | Dart_SetReturnValue(args, retval); 118 | } 119 | 120 | void sync_close(Dart_NativeArguments args) { 121 | int64_t sockfd; // args[0] 122 | Dart_Handle handle; 123 | 124 | handle = Dart_IntegerToInt64(Dart_GetNativeArgument(args, 0), &sockfd); 125 | if (Dart_IsError(handle)) Dart_PropagateError(handle); 126 | 127 | close(static_cast(sockfd)); 128 | } 129 | 130 | void freeData(void* isolate_callback_data, Dart_WeakPersistentHandle handle, 131 | void* buffer) { 132 | free(buffer); 133 | } 134 | 135 | void sync_read(Dart_NativeArguments args) { 136 | int64_t sockfd; // args[0] 137 | uint64_t length; // args[1] 138 | uint8_t *buffer, *data; 139 | int bytes_read; 140 | 141 | Dart_Handle handle; 142 | 143 | handle = Dart_IntegerToInt64(Dart_GetNativeArgument(args, 0), &sockfd); 144 | if (Dart_IsError(handle)) Dart_PropagateError(handle); 145 | 146 | handle = Dart_IntegerToUint64(Dart_GetNativeArgument(args, 1), &length); 147 | if (Dart_IsError(handle)) Dart_PropagateError(handle); 148 | 149 | buffer = reinterpret_cast(malloc(length * sizeof(uint8_t))); 150 | 151 | bytes_read = read(static_cast(sockfd), buffer, static_cast(length)); 152 | 153 | if (bytes_read < 0) { 154 | free(buffer); 155 | Dart_Handle error = NewDartExceptionWithMessage( 156 | "dart:io", "SocketException", "Error reading from socket"); 157 | if (Dart_IsError(error)) Dart_PropagateError(error); 158 | Dart_ThrowException(error); 159 | } 160 | 161 | data = reinterpret_cast(malloc(bytes_read * sizeof(uint8_t))); 162 | memcpy(data, buffer, bytes_read); 163 | free(buffer); 164 | 165 | Dart_Handle result = 166 | Dart_NewExternalTypedData(Dart_TypedData_kUint8, data, bytes_read); 167 | if (Dart_IsError(result)) Dart_PropagateError(result); 168 | 169 | Dart_NewWeakPersistentHandle(result, data, bytes_read, freeData); 170 | 171 | Dart_SetReturnValue(args, result); 172 | } 173 | 174 | void sync_write(Dart_NativeArguments args) { 175 | int64_t sockfd; // args[0] 176 | char* bytes; 177 | intptr_t length; 178 | int64_t byte; 179 | Dart_Handle list; 180 | int i; 181 | 182 | Dart_Handle handle; 183 | 184 | handle = Dart_IntegerToInt64(Dart_GetNativeArgument(args, 0), &sockfd); 185 | if (Dart_IsError(handle)) Dart_PropagateError(handle); 186 | 187 | list = Dart_GetNativeArgument(args, 1); 188 | handle = Dart_ListLength(list, &length); 189 | if (Dart_IsError(handle)) Dart_PropagateError(handle); 190 | 191 | bytes = reinterpret_cast(malloc(length * sizeof(char))); 192 | 193 | for (i = 0; i < length; i++) { 194 | handle = Dart_IntegerToInt64(Dart_ListGetAt(list, i), &byte); 195 | if (Dart_IsError(handle)) Dart_PropagateError(handle); 196 | 197 | bytes[i] = static_cast(byte); 198 | } 199 | 200 | if (write(static_cast(sockfd), bytes, length) != length) { 201 | free(bytes); 202 | Dart_Handle error = NewDartExceptionWithMessage( 203 | "dart:io", "SocketException", "Error writing to socket"); 204 | if (Dart_IsError(error)) Dart_PropagateError(error); 205 | Dart_ThrowException(error); 206 | } 207 | free(bytes); 208 | } 209 | 210 | Dart_NativeFunction ResolveName(Dart_Handle name, int argc, 211 | bool* auto_setup_scope) { 212 | if (!Dart_IsString(name)) return NULL; 213 | Dart_NativeFunction result = NULL; 214 | const char* cname; 215 | Dart_Handle handle; 216 | 217 | handle = Dart_StringToCString(name, &cname); 218 | if (Dart_IsError(handle)) Dart_PropagateError(handle); 219 | 220 | if (strcmp("connect", cname) == 0) result = sync_connect; 221 | if (strcmp("close", cname) == 0) result = sync_close; 222 | if (strcmp("read", cname) == 0) result = sync_read; 223 | if (strcmp("write", cname) == 0) result = sync_write; 224 | 225 | return result; 226 | } 227 | 228 | Dart_Handle NewDartExceptionWithMessage(const char* library_url, 229 | const char* exception_name, 230 | const char* message) { 231 | // Create a Dart Exception object with a message. 232 | Dart_Handle type = 233 | Dart_GetType(Dart_LookupLibrary(Dart_NewStringFromCString(library_url)), 234 | Dart_NewStringFromCString(exception_name), 0, NULL); 235 | 236 | if (Dart_IsError(type)) { 237 | Dart_PropagateError(type); 238 | } 239 | if (message != NULL) { 240 | Dart_Handle args[1]; 241 | args[0] = Dart_NewStringFromCString(message); 242 | if (Dart_IsError(args[0])) { 243 | Dart_PropagateError(args[0]); 244 | } 245 | return Dart_New(type, Dart_Null(), 1, args); 246 | } else { 247 | return Dart_New(type, Dart_Null(), 0, NULL); 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /lib/src/http_client.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | part of sync.socket; 16 | 17 | /** 18 | * A simple synchronous HTTP client. 19 | * 20 | * This is a two-step process. When a [HttpClientRequestSync] is returned the 21 | * underlying network connection has been established, but no data has yet been 22 | * sent. The HTTP headers and body can be set on the request, and close is 23 | * called to send it to the server and get the [HttpClientResponseSync]. 24 | */ 25 | class HttpClientSync { 26 | HttpClientRequestSync getUrl(Uri uri) => 27 | new HttpClientRequestSync._('GET', uri, false); 28 | 29 | HttpClientRequestSync postUrl(uri) => 30 | new HttpClientRequestSync._('POST', uri, true); 31 | 32 | HttpClientRequestSync deleteUrl(uri) => 33 | new HttpClientRequestSync._('DELETE', uri, false); 34 | 35 | HttpClientRequestSync putUrl(uri) => 36 | new HttpClientRequestSync._('PUT', uri, true); 37 | } 38 | 39 | /** 40 | * HTTP request for a synchronous client connection. 41 | */ 42 | class HttpClientRequestSync { 43 | static const PROTOCOL_VERSION = '1.1'; 44 | 45 | int get contentLength => hasBody ? _body.length : null; 46 | 47 | HttpHeaders _headers; 48 | 49 | HttpHeaders get headers { 50 | if (_headers == null) { 51 | _headers = new _HttpClientRequestSyncHeaders(this); 52 | } 53 | return _headers; 54 | } 55 | 56 | final String method; 57 | 58 | final Uri uri; 59 | 60 | final Encoding encoding = UTF8; 61 | 62 | final BytesBuilder _body; 63 | 64 | final SocketSync _socket; 65 | 66 | HttpClientRequestSync._(this.method, Uri uri, bool body) 67 | : this.uri = uri, 68 | this._body = body ? new BytesBuilder() : null, 69 | this._socket = new SocketSync(uri.host, uri.port); 70 | 71 | /** 72 | * Write content into the body. 73 | */ 74 | void write(Object obj) { 75 | if (hasBody) { 76 | _body.add(encoding.encoder.convert(obj.toString())); 77 | } else { 78 | throw new StateError('write not allowed for method $method'); 79 | } 80 | } 81 | 82 | bool get hasBody => _body != null; 83 | 84 | /** 85 | * Send the HTTP request and get the response. 86 | */ 87 | HttpClientResponseSync close() { 88 | _socket.writeAsString('$method ${uri.path} HTTP/$PROTOCOL_VERSION\r\n'); 89 | headers.forEach((name, values) { 90 | values.forEach((value) { 91 | _socket.writeAsString('$name: $value\r\n'); 92 | }); 93 | }); 94 | _socket.writeAsString('\r\n'); 95 | if (hasBody) { 96 | _socket.writeAsBytes(_body.takeBytes()); 97 | } 98 | 99 | return new HttpClientResponseSync(_socket); 100 | } 101 | } 102 | 103 | class _HttpClientRequestSyncHeaders implements HttpHeaders { 104 | Map _headers = >{}; 105 | 106 | final HttpClientRequestSync _request; 107 | ContentType contentType; 108 | 109 | _HttpClientRequestSyncHeaders(this._request); 110 | 111 | @override 112 | List operator [](String name) { 113 | switch (name) { 114 | case HttpHeaders.ACCEPT_CHARSET: 115 | return ['utf-8']; 116 | case HttpHeaders.ACCEPT_ENCODING: 117 | return ['identity']; 118 | case HttpHeaders.CONNECTION: 119 | return ['close']; 120 | case HttpHeaders.CONTENT_LENGTH: 121 | if (!_request.hasBody) { 122 | return null; 123 | } 124 | return [contentLength.toString()]; 125 | case HttpHeaders.CONTENT_TYPE: 126 | if (contentType == null) { 127 | return null; 128 | } 129 | return [contentType.toString()]; 130 | case HttpHeaders.HOST: 131 | return ['$host:$port']; 132 | default: 133 | var values = _headers[name]; 134 | if (values == null || values.isEmpty) { 135 | return null; 136 | } 137 | return values.map((e) => e.toString()).toList(growable: false); 138 | } 139 | } 140 | 141 | @override 142 | void add(String name, Object value) { 143 | switch (name) { 144 | case HttpHeaders.ACCEPT_CHARSET: 145 | case HttpHeaders.ACCEPT_ENCODING: 146 | case HttpHeaders.CONNECTION: 147 | case HttpHeaders.CONTENT_LENGTH: 148 | case HttpHeaders.DATE: 149 | case HttpHeaders.EXPIRES: 150 | case HttpHeaders.IF_MODIFIED_SINCE: 151 | case HttpHeaders.HOST: 152 | throw new UnsupportedError('Unsupported or immutable property: $name'); 153 | case HttpHeaders.CONTENT_TYPE: 154 | contentType = value; 155 | break; 156 | default: 157 | if (_headers[name] == null) { 158 | _headers[name] = []; 159 | } 160 | _headers[name].add(value); 161 | } 162 | } 163 | 164 | @override 165 | void remove(String name, Object value) { 166 | switch (name) { 167 | case HttpHeaders.ACCEPT_CHARSET: 168 | case HttpHeaders.ACCEPT_ENCODING: 169 | case HttpHeaders.CONNECTION: 170 | case HttpHeaders.CONTENT_LENGTH: 171 | case HttpHeaders.DATE: 172 | case HttpHeaders.EXPIRES: 173 | case HttpHeaders.IF_MODIFIED_SINCE: 174 | case HttpHeaders.HOST: 175 | throw new UnsupportedError('Unsupported or immutable property: $name'); 176 | case HttpHeaders.CONTENT_TYPE: 177 | if (contentType == value) { 178 | contentType = null; 179 | } 180 | break; 181 | default: 182 | if (_headers[name] != null) { 183 | _headers[name].remove(value); 184 | if (_headers[name].isEmpty) { 185 | _headers.remove(name); 186 | } 187 | } 188 | } 189 | } 190 | 191 | @override 192 | void removeAll(String name) { 193 | switch (name) { 194 | case HttpHeaders.ACCEPT_CHARSET: 195 | case HttpHeaders.ACCEPT_ENCODING: 196 | case HttpHeaders.CONNECTION: 197 | case HttpHeaders.CONTENT_LENGTH: 198 | case HttpHeaders.DATE: 199 | case HttpHeaders.EXPIRES: 200 | case HttpHeaders.IF_MODIFIED_SINCE: 201 | case HttpHeaders.HOST: 202 | throw new UnsupportedError('Unsupported or immutable property: $name'); 203 | case HttpHeaders.CONTENT_TYPE: 204 | contentType = null; 205 | break; 206 | default: 207 | _headers.remove(name); 208 | } 209 | } 210 | 211 | @override 212 | void set(String name, Object value) { 213 | removeAll(name); 214 | add(name, value); 215 | } 216 | 217 | @override 218 | String value(String name) { 219 | var val = this[name]; 220 | if (val == null || val.isEmpty) { 221 | return null; 222 | } else if (val.length == 1) { 223 | return val[0]; 224 | } else { 225 | throw new HttpException('header $name has more than one value'); 226 | } 227 | } 228 | 229 | @override 230 | void forEach(void f(String name, List values)) { 231 | void forEachFunc(name) { 232 | var values = this[name]; 233 | if (values != null && values.isNotEmpty) { 234 | f(name, values); 235 | } 236 | } 237 | 238 | [ 239 | HttpHeaders.ACCEPT_CHARSET, 240 | HttpHeaders.ACCEPT_ENCODING, 241 | HttpHeaders.CONNECTION, 242 | HttpHeaders.CONTENT_LENGTH, 243 | HttpHeaders.CONTENT_TYPE, 244 | HttpHeaders.HOST 245 | ].forEach(forEachFunc); 246 | _headers.keys.forEach(forEachFunc); 247 | } 248 | 249 | @override 250 | bool get chunkedTransferEncoding => null; 251 | 252 | @override 253 | void set chunkedTransferEncoding(bool _chunkedTransferEncoding) { 254 | throw new UnsupportedError('chunked transfer is unsupported'); 255 | } 256 | 257 | @override 258 | int get contentLength => _request.contentLength; 259 | 260 | @override 261 | void set contentLength(int _contentLength) { 262 | throw new UnsupportedError('content length is automatically set'); 263 | } 264 | 265 | @override 266 | void set date(DateTime _date) { 267 | throw new UnsupportedError('date is unsupported'); 268 | } 269 | 270 | @override 271 | DateTime get date => null; 272 | 273 | @override 274 | void set expires(DateTime _expires) { 275 | throw new UnsupportedError('expires is unsupported'); 276 | } 277 | 278 | @override 279 | DateTime get expires => null; 280 | 281 | @override 282 | void set host(String _host) { 283 | throw new UnsupportedError('host is automatically set'); 284 | } 285 | 286 | @override 287 | String get host => _request.uri.host; 288 | 289 | @override 290 | DateTime get ifModifiedSince => null; 291 | 292 | @override 293 | void set ifModifiedSince(DateTime _ifModifiedSince) { 294 | throw new UnsupportedError('if modified since is unsupported'); 295 | } 296 | 297 | @override 298 | void noFolding(String name) { 299 | throw new UnsupportedError('no folding is unsupported'); 300 | } 301 | @override 302 | bool get persistentConnection => false; 303 | 304 | @override 305 | void set persistentConnection(bool _persistentConnection) { 306 | throw new UnsupportedError('persistence connections are unsupported'); 307 | } 308 | 309 | @override 310 | void set port(int _port) { 311 | throw new UnsupportedError('port is automatically set'); 312 | } 313 | 314 | @override 315 | int get port => _request.uri.port; 316 | 317 | @override 318 | void clear() { 319 | contentType = null; 320 | _headers.clear(); 321 | } 322 | } 323 | 324 | /** 325 | * HTTP response for a cleint connection. 326 | */ 327 | class HttpClientResponseSync { 328 | int get contentLength => headers.contentLength; 329 | final HttpHeaders headers; 330 | final String reasonPhrase; 331 | final int statusCode; 332 | final String body; 333 | 334 | factory HttpClientResponseSync(SocketSync socket) { 335 | int statusCode; 336 | String reasonPhrase; 337 | StringBuffer body = new StringBuffer(); 338 | Map> headers = {}; 339 | 340 | bool inHeader = false; 341 | bool inBody = false; 342 | int contentLength = 0; 343 | int contentRead = 0; 344 | 345 | void processLine(String line, int bytesRead, _LineDecoder decoder) { 346 | if (inBody) { 347 | body.write(line); 348 | contentRead += bytesRead; 349 | } else if (inHeader) { 350 | if (line.trim().isEmpty) { 351 | inBody = true; 352 | if (contentLength > 0) { 353 | decoder.expectedByteCount = contentLength; 354 | } 355 | return; 356 | } 357 | int separator = line.indexOf(':'); 358 | String name = line.substring(0, separator).toLowerCase().trim(); 359 | String value = line.substring(separator + 1).trim(); 360 | if (name == HttpHeaders.TRANSFER_ENCODING && 361 | value.toLowerCase() != 'identity') { 362 | throw new UnsupportedError( 363 | 'only identity transfer encoding is accepted'); 364 | } 365 | if (name == HttpHeaders.CONTENT_LENGTH) { 366 | contentLength = int.parse(value); 367 | } 368 | if (!headers.containsKey(name)) { 369 | headers[name] = []; 370 | } 371 | headers[name].add(value); 372 | } else if (line.startsWith('HTTP/1.1') || line.startsWith('HTTP/1.0')) { 373 | statusCode = int 374 | .parse(line.substring('HTTP/1.x '.length, 'HTTP/1.x xxx'.length)); 375 | reasonPhrase = line.substring('HTTP/1.x xxx '.length); 376 | inHeader = true; 377 | } else { 378 | throw new UnsupportedError('unsupported http response format'); 379 | } 380 | } 381 | 382 | var lineDecoder = new _LineDecoder.withCallback(processLine); 383 | 384 | try { 385 | while (!inHeader || 386 | !inBody || 387 | contentRead + lineDecoder.bufferedBytes < contentLength) { 388 | var bytes = socket.readAsBytes(all: false); 389 | 390 | if (bytes.length == 0) { 391 | break; 392 | } 393 | lineDecoder.add(bytes); 394 | } 395 | } finally { 396 | try { 397 | lineDecoder.close(); 398 | } finally { 399 | socket.close(); 400 | } 401 | } 402 | 403 | return new HttpClientResponseSync._( 404 | reasonPhrase: reasonPhrase, 405 | statusCode: statusCode, 406 | body: body.toString(), 407 | headers: headers); 408 | } 409 | 410 | HttpClientResponseSync._( 411 | {this.reasonPhrase, this.statusCode, this.body, headers}) 412 | : this.headers = new _HttpClientResponseSyncHeaders(headers); 413 | } 414 | 415 | class _HttpClientResponseSyncHeaders implements HttpHeaders { 416 | final Map> _headers; 417 | 418 | _HttpClientResponseSyncHeaders(this._headers); 419 | 420 | @override 421 | List operator [](String name) => _headers[name]; 422 | 423 | @override 424 | void add(String name, Object value) { 425 | throw new UnsupportedError('Response headers are immutable'); 426 | } 427 | 428 | @override 429 | bool get chunkedTransferEncoding => null; 430 | 431 | @override 432 | void set chunkedTransferEncoding(bool _chunkedTransferEncoding) { 433 | throw new UnsupportedError('Response headers are immutable'); 434 | } 435 | 436 | @override 437 | int get contentLength { 438 | var val = value(HttpHeaders.CONTENT_LENGTH); 439 | return int.parse(val, onError: (_) => null); 440 | } 441 | 442 | @override 443 | void set contentLength(int _contentLength) { 444 | throw new UnsupportedError('Response headers are immutable'); 445 | } 446 | 447 | @override 448 | ContentType get contentType { 449 | var val = value(HttpHeaders.CONTENT_TYPE); 450 | if (val != null) { 451 | return ContentType.parse(val); 452 | } 453 | return null; 454 | } 455 | 456 | @override 457 | void set contentType(ContentType _contentType) { 458 | throw new UnsupportedError('Response headers are immutable'); 459 | } 460 | 461 | @override 462 | void set date(DateTime _date) { 463 | throw new UnsupportedError('Response headers are immutable'); 464 | } 465 | 466 | @override 467 | DateTime get date { 468 | var val = value(HttpHeaders.DATE); 469 | if (val != null) { 470 | return DateTime.parse(val); 471 | } 472 | return null; 473 | } 474 | 475 | @override 476 | void set expires(DateTime _expires) { 477 | throw new UnsupportedError('Response headers are immutable'); 478 | } 479 | 480 | @override 481 | DateTime get expires { 482 | var val = value(HttpHeaders.EXPIRES); 483 | if (val != null) { 484 | return DateTime.parse(val); 485 | } 486 | return null; 487 | } 488 | 489 | @override 490 | void forEach(void f(String name, List values)) => _headers.forEach(f); 491 | 492 | @override 493 | void set host(String _host) { 494 | throw new UnsupportedError('Response headers are immutable'); 495 | } 496 | 497 | @override 498 | String get host { 499 | var val = value(HttpHeaders.HOST); 500 | if (val != null) { 501 | return Uri.parse(val).host; 502 | } 503 | return null; 504 | } 505 | 506 | @override 507 | DateTime get ifModifiedSince { 508 | var val = value(HttpHeaders.IF_MODIFIED_SINCE); 509 | if (val != null) { 510 | return DateTime.parse(val); 511 | } 512 | return null; 513 | } 514 | 515 | @override 516 | void set ifModifiedSince(DateTime _ifModifiedSince) { 517 | throw new UnsupportedError('Response headers are immutable'); 518 | } 519 | 520 | @override 521 | void noFolding(String name) { 522 | throw new UnsupportedError('Response headers are immutable'); 523 | } 524 | 525 | @override 526 | bool get persistentConnection => false; 527 | 528 | @override 529 | void set persistentConnection(bool _persistentConnection) { 530 | throw new UnsupportedError('Response headers are immutable'); 531 | } 532 | 533 | @override 534 | void set port(int _port) { 535 | throw new UnsupportedError('Response headers are immutable'); 536 | } 537 | 538 | @override 539 | int get port { 540 | var val = value(HttpHeaders.HOST); 541 | if (val != null) { 542 | return Uri.parse(val).port; 543 | } 544 | return null; 545 | } 546 | @override 547 | void remove(String name, Object value) { 548 | throw new UnsupportedError('Response headers are immutable'); 549 | } 550 | 551 | @override 552 | void removeAll(String name) { 553 | throw new UnsupportedError('Response headers are immutable'); 554 | } 555 | 556 | @override 557 | void set(String name, Object value) { 558 | throw new UnsupportedError('Response headers are immutable'); 559 | } 560 | 561 | @override 562 | String value(String name) { 563 | var val = this[name]; 564 | if (val == null || val.isEmpty) { 565 | return null; 566 | } else if (val.length == 1) { 567 | return val[0]; 568 | } else { 569 | throw new HttpException('header $name has more than one value'); 570 | } 571 | } 572 | 573 | @override 574 | void clear() { 575 | throw new UnsupportedError('Response headers are immutable'); 576 | } 577 | } 578 | --------------------------------------------------------------------------------