-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: implement first version of HTTP(S) binding
- Loading branch information
Showing
5 changed files
with
363 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,212 @@ | ||
// Copyright 2022 The NAMIB Project Developers | ||
// | ||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | ||
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license | ||
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your | ||
// option. This file may not be copied, modified, or distributed | ||
// except according to those terms. | ||
// | ||
// SPDX-License-Identifier: MIT OR Apache-2.0 | ||
|
||
import 'dart:convert'; | ||
|
||
import 'package:http/http.dart' as http; | ||
|
||
import '../core/content.dart'; | ||
import '../core/credentials.dart'; | ||
import '../core/operation_type.dart'; | ||
import '../core/protocol_interfaces/protocol_client.dart'; | ||
import '../core/subscription.dart'; | ||
import '../definitions/form.dart'; | ||
import '../definitions/security_scheme.dart'; | ||
import 'http_config.dart'; | ||
|
||
/// Defines the available HTTP request methods. | ||
enum HttpRequestMethod { | ||
/// Corresponds with the GET request method. | ||
get, | ||
|
||
/// Corresponds with the PUT request method. | ||
put, | ||
|
||
/// Corresponds with the POST request method. | ||
post, | ||
|
||
/// Corresponds with the DELETE request method. | ||
delete, | ||
|
||
/// Corresponds with the PATCH request method. | ||
patch, | ||
} | ||
|
||
/// A [ProtocolClient] for the Hypertext Transfer Protocol (HTTP). | ||
class HttpClient extends ProtocolClient { | ||
/// An (optional) custom [HttpConfig] which overrides the default values. | ||
final HttpConfig? _httpConfig; | ||
|
||
/// Creates a new [HttpClient] based on an optional [HttpConfig]. | ||
HttpClient([this._httpConfig]); | ||
|
||
Future<http.Response> _createRequest( | ||
Form form, OperationType operationType, Object? payload) async { | ||
final requestMethod = _getRequestMethod(form, operationType); | ||
|
||
final Future<http.Response> response; | ||
final headers = _getHeadersFromForm(form); | ||
switch (requestMethod) { | ||
case HttpRequestMethod.get: | ||
response = http.get(Uri.parse(form.href), headers: headers); | ||
break; | ||
case HttpRequestMethod.post: | ||
response = | ||
http.post(Uri.parse(form.href), headers: headers, body: payload); | ||
break; | ||
case HttpRequestMethod.delete: | ||
response = | ||
http.delete(Uri.parse(form.href), headers: headers, body: payload); | ||
break; | ||
case HttpRequestMethod.put: | ||
response = | ||
http.put(Uri.parse(form.href), headers: headers, body: payload); | ||
break; | ||
case HttpRequestMethod.patch: | ||
response = | ||
http.patch(Uri.parse(form.href), headers: headers, body: payload); | ||
break; | ||
} | ||
return response; | ||
} | ||
|
||
static Map<String, String> _getHeadersFromForm(Form form) { | ||
final Map<String, String> headers = {}; | ||
|
||
final dynamic formHeaders = form.additionalFields["htv:headers"]; | ||
if (formHeaders is List<Map<String, String>>) { | ||
for (final formHeader in formHeaders) { | ||
final key = formHeader["htv:fieldName"]; | ||
final value = formHeader["htv:fieldValue"]; | ||
|
||
if (key != null && value != null) { | ||
headers[key] = value; | ||
} | ||
} | ||
} | ||
|
||
final contentType = form.contentType; | ||
if (contentType != null) { | ||
headers["Content-Type"] = contentType; | ||
} | ||
|
||
return headers; | ||
} | ||
|
||
Future<String> _getInputFromContent(Content content) async { | ||
final inputBuffer = await content.byteBuffer; | ||
return utf8.decoder | ||
.convert(inputBuffer.asUint8List().toList(growable: false)); | ||
} | ||
|
||
static Content _contentFromResponse(Form form, http.Response response) { | ||
final type = response.headers["Content-Type"] ?? | ||
form.contentType ?? | ||
"application/octet-stream"; | ||
final body = Stream.value(response.bodyBytes); | ||
return Content(type, body); | ||
} | ||
|
||
@override | ||
Future<Content> invokeResource(Form form, Content content) async { | ||
final input = await _getInputFromContent(content); | ||
final response = | ||
await _createRequest(form, OperationType.invokeaction, input); | ||
return _contentFromResponse(form, response); | ||
} | ||
|
||
@override | ||
Future<Content> readResource(Form form) async { | ||
final response = | ||
await _createRequest(form, OperationType.readproperty, null); | ||
return _contentFromResponse(form, response); | ||
} | ||
|
||
@override | ||
bool setSecurity(List<SecurityScheme> metaData, Credentials? credentials) { | ||
// TODO: implement setSecurity | ||
throw UnimplementedError(); | ||
} | ||
|
||
@override | ||
Future<void> start() async { | ||
// Do nothing | ||
// TODO(JKRhb): Check if this enough. | ||
} | ||
|
||
@override | ||
Future<void> stop() async { | ||
// Do nothing | ||
// TODO(JKRhb): Check if this enough. | ||
} | ||
|
||
@override | ||
Future<Content> unsubscribeResource(Form form) { | ||
// TODO: implement unsubscribeResource | ||
throw UnimplementedError(); | ||
} | ||
|
||
@override | ||
Future<void> writeResource(Form form, Content content) async { | ||
final input = await _getInputFromContent(content); | ||
await _createRequest(form, OperationType.writeproperty, input); | ||
} | ||
|
||
@override | ||
Future<Subscription> subscribeResource( | ||
Form form, | ||
void Function(Content content) next, | ||
void Function(Exception error)? error, | ||
void Function()? complete) async { | ||
// TODO: implement subscribeResource | ||
return Subscription(); | ||
} | ||
} | ||
|
||
HttpRequestMethod _requestMethodFromOperationType(OperationType operationType) { | ||
// TODO(JKRhb): Handle observe/subscribe case | ||
switch (operationType) { | ||
case OperationType.readproperty: | ||
return HttpRequestMethod.get; | ||
case OperationType.writeproperty: | ||
return HttpRequestMethod.put; | ||
case OperationType.invokeaction: | ||
return HttpRequestMethod.post; | ||
} | ||
} | ||
|
||
HttpRequestMethod? _requestMethodFromString(String formDefinition) { | ||
switch (formDefinition) { | ||
case "POST": | ||
return HttpRequestMethod.post; | ||
case "PUT": | ||
return HttpRequestMethod.put; | ||
case "DELETE": | ||
return HttpRequestMethod.delete; | ||
case "GET": | ||
return HttpRequestMethod.get; | ||
case "PATCH": | ||
return HttpRequestMethod.patch; | ||
default: | ||
return null; | ||
} | ||
} | ||
|
||
HttpRequestMethod _getRequestMethod(Form form, OperationType operationType) { | ||
final dynamic formDefinition = form.additionalFields["htv:methodName"]; | ||
if (formDefinition is String) { | ||
final requestMethod = _requestMethodFromString(formDefinition); | ||
if (requestMethod != null) { | ||
return requestMethod; | ||
} | ||
} | ||
|
||
return _requestMethodFromOperationType(operationType); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
// Copyright 2022 The NAMIB Project Developers | ||
// | ||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | ||
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license | ||
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your | ||
// option. This file may not be copied, modified, or distributed | ||
// except according to those terms. | ||
// | ||
// SPDX-License-Identifier: MIT OR Apache-2.0 | ||
|
||
import '../core/protocol_interfaces/protocol_client.dart'; | ||
import '../core/protocol_interfaces/protocol_client_factory.dart'; | ||
import 'http_client.dart'; | ||
import 'http_config.dart'; | ||
|
||
/// A [ProtocolClientFactory] that produces HTTP clients. | ||
class HttpClientFactory extends ProtocolClientFactory { | ||
@override | ||
String get scheme => "http"; | ||
|
||
/// The [HttpConfig] used to configure new clients. | ||
final HttpConfig? httpConfig; | ||
|
||
/// Creates a new [HttpClientFactory] based on an optional [HttpConfig]. | ||
HttpClientFactory([this.httpConfig]); | ||
|
||
@override | ||
bool destroy() { | ||
// TODO(JKRhb): Check if there is anything to destroy. | ||
return true; | ||
} | ||
|
||
@override | ||
ProtocolClient createClient() => HttpClient(httpConfig); | ||
|
||
@override | ||
bool init() { | ||
// TODO(JKRhb): Check if there is anything to init. | ||
return true; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
// Copyright 2022 The NAMIB Project Developers | ||
// | ||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | ||
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license | ||
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your | ||
// option. This file may not be copied, modified, or distributed | ||
// except according to those terms. | ||
// | ||
// SPDX-License-Identifier: MIT OR Apache-2.0 | ||
|
||
/// Allows for configuring the behavior of HTTP clients and servers. | ||
class HttpConfig { | ||
/// Custom port number that should be used by a server. | ||
/// | ||
/// Defaults to 80 for HTTP and 443 for HTTPS. | ||
int? port; | ||
|
||
/// Indicates if the client or server should use HTTPS. | ||
bool? secure; | ||
|
||
/// Creates a new [HttpConfig] object. | ||
HttpConfig({this.port, this.secure}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
// Copyright 2022 The NAMIB Project Developers | ||
// | ||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | ||
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license | ||
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your | ||
// option. This file may not be copied, modified, or distributed | ||
// except according to those terms. | ||
// | ||
// SPDX-License-Identifier: MIT OR Apache-2.0 | ||
|
||
import 'package:dart_wot/src/binding_http/http_config.dart'; | ||
|
||
import '../core/credentials.dart'; | ||
import '../core/protocol_interfaces/protocol_server.dart'; | ||
import '../scripting_api/exposed_thing.dart'; | ||
|
||
/// A [ProtocolServer] for the Hypertext Transfer Protocol (HTTP). | ||
class HttpServer extends ProtocolServer { | ||
final String _scheme; | ||
|
||
final int _port; | ||
|
||
final HttpConfig? _httpConfig; | ||
|
||
Map<String, Credentials> _credentials = {}; | ||
|
||
/// Create a new [HttpServer] from an optional [HttpConfig]. | ||
HttpServer(this._httpConfig) | ||
// TODO(JKRhb): Check if the scheme should be determined differently. | ||
: _scheme = _httpConfig?.secure ?? false ? "https" : "http", | ||
_port = _portFromConfig(_httpConfig); | ||
|
||
static int _portFromConfig(HttpConfig? httpConfig) { | ||
final secure = httpConfig?.secure ?? false; | ||
|
||
return httpConfig?.port ?? (secure ? 443 : 80); | ||
} | ||
|
||
@override | ||
Future<void> expose(ExposedThing thing) { | ||
// TODO: implement expose | ||
throw UnimplementedError(); | ||
} | ||
|
||
@override | ||
int get port => _port; | ||
|
||
@override | ||
String get scheme => _scheme; | ||
|
||
@override | ||
Future<void> start(Map<String, Credentials> credentials) async { | ||
_credentials = credentials; | ||
// TODO(JKRhb): implement start | ||
} | ||
|
||
@override | ||
Future<void> stop() async { | ||
// TODO(JKRhb): implement stop | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
// Copyright 2022 The NAMIB Project Developers | ||
// | ||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | ||
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license | ||
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your | ||
// option. This file may not be copied, modified, or distributed | ||
// except according to those terms. | ||
// | ||
// SPDX-License-Identifier: MIT OR Apache-2.0 | ||
|
||
import '../core/protocol_interfaces/protocol_client_factory.dart'; | ||
import 'http_client_factory.dart'; | ||
import 'http_config.dart'; | ||
|
||
/// A [ProtocolClientFactory] that produces HTTPS clients. | ||
// TODO(JKRhb): Not sure if two Factory classes make that much sense. Maybe it | ||
// would be better to have one Factory that has a List of supported | ||
// schemes (i. e. both http and https in this case). | ||
// At the moment, this is the approach taken from node-wot, though. | ||
class HttpsClientFactory extends HttpClientFactory { | ||
@override | ||
String get scheme => "https"; | ||
|
||
/// Creates a new [HttpClientFactory] based on an optional [HttpConfig]. | ||
HttpsClientFactory([HttpConfig? _httpConfig]) : super(_httpConfig); | ||
} |