From a9617331e4763e863a788dd38b2814107387d03a Mon Sep 17 00:00:00 2001 From: Maksim Lin Date: Tue, 26 Mar 2024 12:30:47 +1100 Subject: [PATCH 1/5] add support for HTTPS --- README.md | 34 ++++++++++++++++++------ bin/dhttpd.dart | 6 ++++- lib/dhttpd.dart | 19 ++++++++++++- lib/src/options.dart | 24 +++++++++++++++++ lib/src/options.g.dart | 21 +++++++++++++++ sample/server_chain.pem | 59 +++++++++++++++++++++++++++++++++++++++++ sample/server_key.pem | 29 ++++++++++++++++++++ 7 files changed, 182 insertions(+), 10 deletions(-) create mode 100644 sample/server_chain.pem create mode 100644 sample/server_key.pem diff --git a/README.md b/README.md index 76eb9f7..8ddd0b2 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![CI](https://github.com/kevmoo/dhttpd/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/kevmoo/dhttpd/actions/workflows/ci.yml) [![package publisher](https://img.shields.io/pub/publisher/dhttpd.svg)](https://pub.dev/packages/dhttpd/publisher) -A simple HTTP server that can serve up any directory, built with Dart. +A simple HTTP(S) server that can serve up any directory, built with Dart. Inspired by `python -m SimpleHTTPServer`. ## Install @@ -39,17 +39,35 @@ $ dart run build_runner build -o build $ dhttpd --path build/web/ # Serves app at http://localhost:8080 ``` +### HTTPS + +If you want to use HTTPS you will need to pass in the path of the ssl certificate and the ssl key file as well as the password string, if a password is set on the key: + +``` +$ dart bin/dhttpd.dart --sslcert=sample/server_chain.pem --sslkey=sample/server_key.pem --sslkeypassword=dartdart +Server HTTPS started on port 8080 +``` + ## Configure ```console $ dhttpd --help --p, --port= The port to listen on. - (defaults to "8080") - --path= The path to serve. If not set, the current directory is used. - --headers= HTTP headers to apply to each response. header=value;header2=value - --host= The hostname to listen on. - (defaults to "localhost") --h, --help Displays the help. +-p, --port= The port to listen on. + (defaults to "8080") + --path= The path to serve. If not set, the current directory is used. + --headers= HTTP headers to apply to each response. header=value;header2=value + --host= The hostname to listen on. + (defaults to "localhost") + --sslcert= The SSL certificate to use. + If set along with sslkey, https will be used. + See the dart documentation about SecurityContext.useCertificateChain for more. + --sslkey= The key of the SSL certificate to use. + If set along with sslcert, https will be used. + See the dart documentation about SecurityContext.usePrivateKey for more. + --sslkeypassword= The password for the key of the SSL certificate to use. + Required if the ssl key being used has a password set. + See the dart documentation about SecurityContext.usePrivateKey for more. +-h, --help Displays the help. ``` [path]: https://dart.dev/tools/pub/cmd/pub-global#running-a-script-from-your-path diff --git a/bin/dhttpd.dart b/bin/dhttpd.dart index d23156d..3d64edc 100644 --- a/bin/dhttpd.dart +++ b/bin/dhttpd.dart @@ -25,9 +25,13 @@ Future main(List args) async { headers: options.headers != null ? _parseKeyValuePairs(options.headers!) : null, address: options.host, + sslCert: options.sslcert, + sslKey: options.sslkey, + sslPassword: options.sslkeypassword, ); - print('Server started on port ${options.port}'); + print( + 'Server HTTP${Dhttpd.isSSL ? 'S' : ''} started on port ${options.port}'); } Map _parseKeyValuePairs(String str) => { diff --git a/lib/dhttpd.dart b/lib/dhttpd.dart index ae691bd..82a425b 100644 --- a/lib/dhttpd.dart +++ b/lib/dhttpd.dart @@ -10,6 +10,7 @@ import 'src/options.dart'; class Dhttpd { final HttpServer _server; final String path; + static bool _ssl = false; Dhttpd._(this._server, this.path); @@ -19,6 +20,8 @@ class Dhttpd { String get urlBase => 'http://$host:$port/'; + static bool get isSSL => _ssl; + /// [address] can either be a [String] or an /// [InternetAddress]. If [address] is a [String], [start] will /// perform a [InternetAddress.lookup] and use the first value in the @@ -34,15 +37,29 @@ class Dhttpd { int port = defaultPort, Object address = defaultHost, Map? headers, + String? sslCert, + String? sslKey, + String? sslPassword, }) async { path ??= Directory.current.path; + SecurityContext? securityContext; + if (sslCert != null && sslKey != null) { + securityContext = SecurityContext() + ..useCertificateChain(sslCert) + ..usePrivateKey(sslKey, password: sslPassword); + } + if (securityContext != null) { + _ssl = true; + } + final pipeline = const Pipeline() .addMiddleware(logRequests()) .addMiddleware(_headersMiddleware(headers)) .addHandler(createStaticHandler(path, defaultDocument: 'index.html')); - final server = await io.serve(pipeline, address, port); + final server = await io.serve(pipeline, address, port, + securityContext: securityContext); return Dhttpd._(server, path); } diff --git a/lib/src/options.dart b/lib/src/options.dart index e91ecb4..93d5d9a 100644 --- a/lib/src/options.dart +++ b/lib/src/options.dart @@ -34,6 +34,27 @@ class Options { help: 'The hostname to listen on.') final String host; + @CliOption( + valueHelp: 'sslcert', + help: 'The SSL certificate to use.' + '\r\nIf set along with sslkey, https will be used.' + '\r\nSee the dart documentation about SecurityContext.useCertificateChain for more.') + final String? sslcert; + + @CliOption( + valueHelp: 'sslkey', + help: 'The key of the SSL certificate to use.' + '\r\nIf set along with sslcert, https will be used.' + '\r\nSee the dart documentation about SecurityContext.usePrivateKey for more.') + final String? sslkey; + + @CliOption( + valueHelp: 'sslkeypassword', + help: 'The password for the key of the SSL certificate to use.' + '\r\nRequired if the ssl key being used has a password set.' + '\r\nSee the dart documentation about SecurityContext.usePrivateKey for more.') + final String? sslkeypassword; + @CliOption(abbr: 'h', negatable: false, help: 'Displays the help.') final bool help; @@ -42,6 +63,9 @@ class Options { this.path, this.headers, required this.host, + this.sslcert, + this.sslkey, + this.sslkeypassword, required this.help, }); } diff --git a/lib/src/options.g.dart b/lib/src/options.g.dart index c6bc822..a80c0cd 100644 --- a/lib/src/options.g.dart +++ b/lib/src/options.g.dart @@ -25,6 +25,9 @@ Options _$parseOptionsResult(ArgResults result) => Options( path: result['path'] as String?, headers: result['headers'] as String?, host: result['host'] as String, + sslcert: result['sslcert'] as String?, + sslkey: result['sslkey'] as String?, + sslkeypassword: result['sslkeypassword'] as String?, help: result['help'] as bool, ); @@ -52,6 +55,24 @@ ArgParser _$populateOptionsParser(ArgParser parser) => parser valueHelp: 'host', defaultsTo: 'localhost', ) + ..addOption( + 'sslcert', + help: + 'The SSL certificate to use.\r\nIf set along with sslkey, https will be used.\r\nSee the dart documentation about SecurityContext.useCertificateChain for more.', + valueHelp: 'sslcert', + ) + ..addOption( + 'sslkey', + help: + 'The key of the SSL certificate to use.\r\nIf set along with sslcert, https will be used.\r\nSee the dart documentation about SecurityContext.usePrivateKey for more.', + valueHelp: 'sslkey', + ) + ..addOption( + 'sslkeypassword', + help: + 'The password for the key of the SSL certificate to use.\r\nRequired if the ssl key being used has a password set.\r\nSee the dart documentation about SecurityContext.usePrivateKey for more.', + valueHelp: 'sslkeypassword', + ) ..addFlag( 'help', abbr: 'h', diff --git a/sample/server_chain.pem b/sample/server_chain.pem new file mode 100644 index 0000000..341a86f --- /dev/null +++ b/sample/server_chain.pem @@ -0,0 +1,59 @@ +-----BEGIN CERTIFICATE----- +MIIDZDCCAkygAwIBAgIBATANBgkqhkiG9w0BAQsFADAgMR4wHAYDVQQDDBVpbnRl +cm1lZGlhdGVhdXRob3JpdHkwHhcNMTUxMDI3MTAyNjM1WhcNMjUxMDI0MTAyNjM1 +WjAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQCkg/Qr8RQeLTOSgCkyiEX2ztgkgscX8hKGHEHdvlkmVK3JVEIIwkvu +/Y9LtHZUia3nPAgqEEbexzTENZjSCcC0V6I2XW/e5tIE3rO0KLZyhtZhN/2SfJ6p +KbOh0HLr1VtkKJGp1tzUmHW/aZI32pK60ZJ/N917NLPCJpCaL8+wHo3+w3oNqln6 +oJsfgxy9SUM8Bsc9WMYKMUdqLO1QKs1A5YwqZuO7Mwj+4LY2QDixC7Ua7V9YAPo2 +1SBeLvMCHbYxSPCuxcZ/kDkgax/DF9u7aZnGhMImkwBka0OQFvpfjKtTIuoobTpe +PAG7MQYXk4RjnjdyEX/9XAQzvNo1CDObAgMBAAGjgbQwgbEwPAYDVR0RBDUwM4IJ +bG9jYWxob3N0ggkxMjcuMC4wLjGCAzo6MYcEfwAAAYcQAAAAAAAAAAAAAAAAAAAA +ATAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBSvhJo6taTggJQBukEvMo/PDk8tKTAf +BgNVHSMEGDAWgBS98L4T5RaIToE3DkBRsoeWPil0eDAOBgNVHQ8BAf8EBAMCA6gw +EwYDVR0lBAwwCgYIKwYBBQUHAwEwDQYJKoZIhvcNAQELBQADggEBAHLOt0mL2S4A +B7vN7KsfQeGlVgZUVlEjem6kqBh4fIzl4CsQuOO8oJ0FlO1z5JAIo98hZinymJx1 +phBVpyGIKakT/etMH0op5evLe9dD36VA3IM/FEv5ibk35iGnPokiJXIAcdHd1zam +YaTHRAnZET5S03+7BgRTKoRuszhbvuFz/vKXaIAnVNOF4Gf2NUJ/Ax7ssJtRkN+5 +UVxe8TZVxzgiRv1uF6NTr+J8PDepkHCbJ6zEQNudcFKAuC56DN1vUe06gRDrNbVq +2JHEh4pRfMpdsPCrS5YHBjVq/XHtFHgwDR6g0WTwSUJvDeM4OPQY5f61FB0JbFza +PkLkXmoIod8= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDLjCCAhagAwIBAgIBAjANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDDA1yb290 +YXV0aG9yaXR5MB4XDTE1MTAyNzEwMjYzNVoXDTI1MTAyNDEwMjYzNVowIDEeMBwG +A1UEAwwVaW50ZXJtZWRpYXRlYXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEA6GndRFiXk+2q+Ig7ZOWKKGta+is8137qyXz+eVFs5sA0ajMN +ZBAMWS0TIXw/Yks+y6fEcV/tfv91k1eUN4YXPcoxTdDF97d2hO9wxumeYOMnQeDy +VZVDKQBZ+jFMeI+VkNpMEdmsLErpZDGob/1dC8tLEuR6RuRR8X6IDGMPOCMw1jLK +V1bQjPtzqKadTscfjLuKxuLgspJdTrzsu6hdcl1mm8K6CjTY2HNXWxs1yYmwfuQ2 +Z4/8sOMNqFqLjN+ChD7pksTMq7IosqGiJzi2bpd5f44ek/k822Y0ATncJHk4h1Z+ +kZBnW6kgcLna1gDri9heRwSZ+M8T8nlHgIMZIQIDAQABo3sweTASBgNVHRMBAf8E +CDAGAQH/AgEAMB0GA1UdDgQWBBS98L4T5RaIToE3DkBRsoeWPil0eDAfBgNVHSME +GDAWgBRxD5DQHTmtpDFKDOiMf5FAi6vfbzAOBgNVHQ8BAf8EBAMCAgQwEwYDVR0l +BAwwCgYIKwYBBQUHAwEwDQYJKoZIhvcNAQELBQADggEBAD+4KpUeV5mUPw5IG/7w +eOXnUpeS96XFGuS1JuFo/TbgntPWSPyo+rD4GrPIkUXyoHaMCDd2UBEjyGbBIKlB +NZA3RJOAEp7DTkLNK4RFn/OEcLwG0J5brL7kaLRO4vwvItVIdZ2XIqzypRQTc0MG +MmF08zycnSlaN01ryM67AsMhwdHqVa+uXQPo8R8sdFGnZ33yywTYD73FeImXilQ2 +rDnFUVqmrW1fjl0Fi4rV5XI0EQiPrzKvRtmF8ZqjGATPOsRd64cwQX6V+P5hNeIR +9pba6td7AbNGausHfacRYMyoGJWWWkFPd+7jWOCPqW7Fk1tmBgdB8GzXa3inWIRM +RUE= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIC+zCCAeOgAwIBAgIBATANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDDA1yb290 +YXV0aG9yaXR5MB4XDTE1MTAyNzEwMjYzNFoXDTI1MTAyNDEwMjYzNFowGDEWMBQG +A1UEAwwNcm9vdGF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAMl+dcraUM/E7E6zl7+7hK9oUJYXJLnfiMtP/TRFVbH4+2aEN8vXzPbzKdR3 +FfaHczXQTwnTCaYA4u4uSDvSOsFFEfxEwYORsdKmQEM8nGpVX2NVvKsMcGIhh8kh +ZwJfkMIOcAxmGIHGdMhF8VghonJ8uGiuqktxdfpARq0g3fqIjDHsF9/LpfshUfk9 +wsRyTF0yr90U/dsfnE+u8l7GvVl8j2Zegp0sagAGtLaNv7tP17AibqEGg2yDBrBN +9r9ihe4CqMjx+Q2kQ2S9Gz2V2ReO/n6vm2VQxsPRB/lV/9jh7cUcS0/9mggLYrDy +cq1v7rLLQrWuxMz1E3gOhyCYJ38CAwEAAaNQME4wHQYDVR0OBBYEFHEPkNAdOa2k +MUoM6Ix/kUCLq99vMB8GA1UdIwQYMBaAFHEPkNAdOa2kMUoM6Ix/kUCLq99vMAwG +A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBABrhjnWC6b+z9Kw73C/niOwo +9sPdufjS6tb0sCwDjt3mjvE4NdNWt+/+ZOugW6dqtvqhtqZM1q0u9pJkNwIrqgFD +ZHcfNaf31G6Z2YE+Io7woTVw6fFobg/EFo+a/qwbvWL26McmiRL5yiSBjVjpX4a5 +kdZ+aPQUCBaLrTWwlCDqzSVIULWUQvveRWbToMFKPNID58NtEpymAx3Pgir7YjV9 +UnlU2l5vZrh1PTCqZxvC/IdRESUfW80LdHaeyizRUP+6vKxGgSz2MRuYINjbd6GO +hGiCpWlwziW2xLV1l2qSRLko2kIafLZP18N0ThM9zKbU5ps9NgFOf//wqSGtLaE= +-----END CERTIFICATE----- diff --git a/sample/server_key.pem b/sample/server_key.pem new file mode 100644 index 0000000..895b7d2 --- /dev/null +++ b/sample/server_key.pem @@ -0,0 +1,29 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIE4zAcBgoqhkiG9w0BDAEBMA4ECBMCjlg8JYZ4AgIIAASCBMFd9cBoZ5xcTock +AVQcg/HzYJtMceKn1gtMDdC7mmXuyN0shoxhG4BpQInHkFARL+nenesXFxEm4X5e +L603Pcgw72/ratxVpTW7hPMjiLTEBqza0GjQm7Sarbdy+Vzdp/6XFrAcPfFl1juY +oyYzbozPsvFHz3Re44y1KmI4HAzU/qkjJUbNTTiPPVI2cDP6iYN2XXxBb1wwp8jR +iqdZqFG7lU/wvPEbD7BVPpmJBHWNG681zb4ea5Zn4hW8UaxpiIBiaH0/IWc2SVZd +RliAFo3NEsGxCcsnBo/n00oudGbOJxdOp7FbH5hJpeqX2WhCyJRxIeHOWmeuMAet +03HFriiEmJ99m2nEJN1x0A3QUUM7ji6vZAb4qb1dyq7LlX4M2aaqixRnaTcQkapf +DOxX35DEBXSKrDpyWp6Rx4wNpUyi1TKyhaVnYgD3Gn0VfC/2w86gSFlrf9PMYGM0 +PvFxTDzTyjOuPBRa728gZOGXgDOL7qvdInU/opVew7kFeRQHXxHzFCLK5dD+Vrig +5fS3m0++f55ODkxqHXB8gbXbd3GMmsW6MrGpU7VsCNtbVPdSMW0FalovEB0M+2lj +1VfuvL+0F5huTe+BgZAt6xgET/CIcZXdNMRPVhraqUjqWtI9Rdk4STPCpU1rDkjG +YDl/fo4W2T6qQWFUpiC9IvVVGkVxaqfZZ4Qu+V5xPUi6vk95QiTNkN1t+m+sCCgS +Llkea8Um0aHMy33Lj3NsfL0LMrnpniqcAks8BvcgIZwk1VRqcj7BQVCygJSYrmAR +DBhMpjWlXuSggnyVPuduZDtnTN+8lCHLOKL3a3bDb6ySaKX49Km6GutDLfpDtEA0 +3mQvmEG4XVm7zy+AlN72qFbtSLDRi/D/uQh2q/ZrFQLOBQBQB56TvEbKouLimUDM +ascQA3aUyhOE7e+d02NOFIFTozwc/C//CIFeA+ZEwxyfha/3Bor6Jez7PC/eHNxZ +w7YMXzPW9NhcCcerhYGebuCJxLwzqJ+IGdukjKsGV2ytWDoB2xZiJNu096j4RKcq +YSJoen0R7IH8N4eDujXR8m9kAl724Uqs1OoAs4VNICvzTutbsgVZ6Z+NMOcfnPw9 +jZkFhot16w8znD+OmhBR7/bzOLpaeUhk7EhNq5M6U0NNWx3WwkDlvU/jx+6/EQe3 +iLEHptH2HYBF1xscaKGbtKNtuQsfdzgWpOX0qK2YbK3yCKvL/xIm1DQmDZDKkWdW +VNh8oGV1H96CivWlvxhAgXKz9F/83CjMw8YXRk7RJvWR4vtNvXFAvGkFIYCN9Jv9 +p+1ukaYoxSLGBik907I6gWSHqumJiCprUyAX/bVfZfNiYh4hzeA3lhwxZSax3JG4 +7QFPvyepOmF/3AAzS/Pusx6jOZnuCMCkfQi6Wpem1o3s4x+fP7kz00Xuj01ErucM +S10ixfIh84kXBN3dTRDtDdeCyoMsBKO0W5jDBBlWL02YfdF6Opo1Q4cPh2DYgXMh +XEszNZSK5LB0y+f3A6Kdx/hkZzHVvMONA70OyrkoZzGyWENhcB0c7ntTJyPPD2qM +s0HRA2VwF/0ypU3OKERM1Ua5NSkTgvnnVTlV9GO90Tkn5v4fxdl8NzIuJLyGguTP +Xc0tRM34Lg== +-----END ENCRYPTED PRIVATE KEY----- From c81ed433afe8ccca7e8fb907ce511258f636b0f1 Mon Sep 17 00:00:00 2001 From: Maksim Lin Date: Wed, 3 Apr 2024 13:51:53 +1100 Subject: [PATCH 2/5] fix line lengths, whitespace lints --- lib/dhttpd.dart | 2 +- lib/src/options.dart | 9 ++++++--- lib/src/options.g.dart | 14 +++++++++----- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/lib/dhttpd.dart b/lib/dhttpd.dart index 82a425b..f54234b 100644 --- a/lib/dhttpd.dart +++ b/lib/dhttpd.dart @@ -51,7 +51,7 @@ class Dhttpd { } if (securityContext != null) { _ssl = true; - } + } final pipeline = const Pipeline() .addMiddleware(logRequests()) diff --git a/lib/src/options.dart b/lib/src/options.dart index 93d5d9a..95caac3 100644 --- a/lib/src/options.dart +++ b/lib/src/options.dart @@ -38,21 +38,24 @@ class Options { valueHelp: 'sslcert', help: 'The SSL certificate to use.' '\r\nIf set along with sslkey, https will be used.' - '\r\nSee the dart documentation about SecurityContext.useCertificateChain for more.') + '\r\nSee the dart documentation about ' + 'SecurityContext.useCertificateChain for more.') final String? sslcert; @CliOption( valueHelp: 'sslkey', help: 'The key of the SSL certificate to use.' '\r\nIf set along with sslcert, https will be used.' - '\r\nSee the dart documentation about SecurityContext.usePrivateKey for more.') + '\r\nSee the dart documentation about ' + 'SecurityContext.usePrivateKey for more.') final String? sslkey; @CliOption( valueHelp: 'sslkeypassword', help: 'The password for the key of the SSL certificate to use.' '\r\nRequired if the ssl key being used has a password set.' - '\r\nSee the dart documentation about SecurityContext.usePrivateKey for more.') + '\r\nSee the dart documentation about ' + 'SecurityContext.usePrivateKey for more.') final String? sslkeypassword; @CliOption(abbr: 'h', negatable: false, help: 'Displays the help.') diff --git a/lib/src/options.g.dart b/lib/src/options.g.dart index a80c0cd..9b8df68 100644 --- a/lib/src/options.g.dart +++ b/lib/src/options.g.dart @@ -57,20 +57,24 @@ ArgParser _$populateOptionsParser(ArgParser parser) => parser ) ..addOption( 'sslcert', - help: - 'The SSL certificate to use.\r\nIf set along with sslkey, https will be used.\r\nSee the dart documentation about SecurityContext.useCertificateChain for more.', + help: 'The SSL certificate to use.\r\nIf set along with sslkey, ' + 'https will be used.\r\nSee the dart documentation about ' + 'SecurityContext.useCertificateChain for more.', valueHelp: 'sslcert', ) ..addOption( 'sslkey', help: - 'The key of the SSL certificate to use.\r\nIf set along with sslcert, https will be used.\r\nSee the dart documentation about SecurityContext.usePrivateKey for more.', + 'The key of the SSL certificate to use.\r\nIf set along with sslcert, ' + 'https will be used.\r\nSee the dart documentation about ' + 'SecurityContext.usePrivateKey for more.', valueHelp: 'sslkey', ) ..addOption( 'sslkeypassword', - help: - 'The password for the key of the SSL certificate to use.\r\nRequired if the ssl key being used has a password set.\r\nSee the dart documentation about SecurityContext.usePrivateKey for more.', + help: 'The password for the key of the SSL certificate to use.\r\nRequired ' + 'if the ssl key being used has a password set.\r\nSee the dart ' + 'documentation about SecurityContext.usePrivateKey for more.', valueHelp: 'sslkeypassword', ) ..addFlag( From e2db1ece62e130aa307e0075cd5e70c6c6d286e7 Mon Sep 17 00:00:00 2001 From: Maksim Lin Date: Thu, 4 Apr 2024 08:38:35 +1100 Subject: [PATCH 3/5] pass sec context into httpd, ditch icky static and having the sec context in the class might be useful for future use --- bin/dhttpd.dart | 4 ++-- lib/dhttpd.dart | 11 ++++------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/bin/dhttpd.dart b/bin/dhttpd.dart index 3d64edc..e5aef84 100644 --- a/bin/dhttpd.dart +++ b/bin/dhttpd.dart @@ -19,7 +19,7 @@ Future main(List args) async { return; } - await Dhttpd.start( + final httpd = await Dhttpd.start( path: options.path, port: options.port, headers: @@ -31,7 +31,7 @@ Future main(List args) async { ); print( - 'Server HTTP${Dhttpd.isSSL ? 'S' : ''} started on port ${options.port}'); + 'Server HTTP${httpd.isSSL ? 'S' : ''} started on port ${options.port}'); } Map _parseKeyValuePairs(String str) => { diff --git a/lib/dhttpd.dart b/lib/dhttpd.dart index f54234b..12df784 100644 --- a/lib/dhttpd.dart +++ b/lib/dhttpd.dart @@ -10,9 +10,9 @@ import 'src/options.dart'; class Dhttpd { final HttpServer _server; final String path; - static bool _ssl = false; + final SecurityContext? _securityContext; - Dhttpd._(this._server, this.path); + Dhttpd._(this._server, this.path, this._securityContext); String get host => _server.address.host; @@ -20,7 +20,7 @@ class Dhttpd { String get urlBase => 'http://$host:$port/'; - static bool get isSSL => _ssl; + bool get isSSL => _securityContext != null; /// [address] can either be a [String] or an /// [InternetAddress]. If [address] is a [String], [start] will @@ -49,9 +49,6 @@ class Dhttpd { ..useCertificateChain(sslCert) ..usePrivateKey(sslKey, password: sslPassword); } - if (securityContext != null) { - _ssl = true; - } final pipeline = const Pipeline() .addMiddleware(logRequests()) @@ -60,7 +57,7 @@ class Dhttpd { final server = await io.serve(pipeline, address, port, securityContext: securityContext); - return Dhttpd._(server, path); + return Dhttpd._(server, path, securityContext); } Future destroy() => _server.close(); From dcd6576be1bea243f9c7e27c36167c6d113996c9 Mon Sep 17 00:00:00 2001 From: Maksim Lin Date: Thu, 4 Apr 2024 08:58:00 +1100 Subject: [PATCH 4/5] trim down SSL help text, update test --- README.md | 16 ++++++---------- lib/src/options.dart | 15 +++------------ lib/src/options.g.dart | 13 +++---------- test/readme_test.dart | 17 ++++++++++------- 4 files changed, 22 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 8ddd0b2..3db8e90 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![CI](https://github.com/kevmoo/dhttpd/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/kevmoo/dhttpd/actions/workflows/ci.yml) [![package publisher](https://img.shields.io/pub/publisher/dhttpd.svg)](https://pub.dev/packages/dhttpd/publisher) -A simple HTTP(S) server that can serve up any directory, built with Dart. +A simple HTTP server that can serve up any directory, built with Dart. Inspired by `python -m SimpleHTTPServer`. ## Install @@ -41,13 +41,15 @@ $ dhttpd --path build/web/ # Serves app at http://localhost:8080 ### HTTPS -If you want to use HTTPS you will need to pass in the path of the ssl certificate and the ssl key file as well as the password string, if a password is set on the key: +If you want to use HTTPS you will need to pass in the path of the SSL certificate and the SSL key file as well as the password string, if a password is set on the key, for example: ``` $ dart bin/dhttpd.dart --sslcert=sample/server_chain.pem --sslkey=sample/server_key.pem --sslkeypassword=dartdart Server HTTPS started on port 8080 ``` +See the Dart documentation of [SecurityContext.usePrivateKey](https://api.dart.dev/stable/3.3.3/dart-io/SecurityContext/usePrivateKeyBytes.html) for more details. + ## Configure ```console @@ -58,15 +60,9 @@ $ dhttpd --help --headers= HTTP headers to apply to each response. header=value;header2=value --host= The hostname to listen on. (defaults to "localhost") - --sslcert= The SSL certificate to use. - If set along with sslkey, https will be used. - See the dart documentation about SecurityContext.useCertificateChain for more. - --sslkey= The key of the SSL certificate to use. - If set along with sslcert, https will be used. - See the dart documentation about SecurityContext.usePrivateKey for more. + --sslcert= The SSL certificate to use. Also requires sslkey + --sslkey= The key of the SSL certificate to use. Also requires sslcert --sslkeypassword= The password for the key of the SSL certificate to use. - Required if the ssl key being used has a password set. - See the dart documentation about SecurityContext.usePrivateKey for more. -h, --help Displays the help. ``` diff --git a/lib/src/options.dart b/lib/src/options.dart index 95caac3..acf6154 100644 --- a/lib/src/options.dart +++ b/lib/src/options.dart @@ -36,26 +36,17 @@ class Options { @CliOption( valueHelp: 'sslcert', - help: 'The SSL certificate to use.' - '\r\nIf set along with sslkey, https will be used.' - '\r\nSee the dart documentation about ' - 'SecurityContext.useCertificateChain for more.') + help: 'The SSL certificate to use. Also requires sslkey') final String? sslcert; @CliOption( valueHelp: 'sslkey', - help: 'The key of the SSL certificate to use.' - '\r\nIf set along with sslcert, https will be used.' - '\r\nSee the dart documentation about ' - 'SecurityContext.usePrivateKey for more.') + help: 'The key of the SSL certificate to use. Also requires sslcert') final String? sslkey; @CliOption( valueHelp: 'sslkeypassword', - help: 'The password for the key of the SSL certificate to use.' - '\r\nRequired if the ssl key being used has a password set.' - '\r\nSee the dart documentation about ' - 'SecurityContext.usePrivateKey for more.') + help: 'The password for the key of the SSL certificate to use.') final String? sslkeypassword; @CliOption(abbr: 'h', negatable: false, help: 'Displays the help.') diff --git a/lib/src/options.g.dart b/lib/src/options.g.dart index 9b8df68..7b96675 100644 --- a/lib/src/options.g.dart +++ b/lib/src/options.g.dart @@ -57,24 +57,17 @@ ArgParser _$populateOptionsParser(ArgParser parser) => parser ) ..addOption( 'sslcert', - help: 'The SSL certificate to use.\r\nIf set along with sslkey, ' - 'https will be used.\r\nSee the dart documentation about ' - 'SecurityContext.useCertificateChain for more.', + help: 'The SSL certificate to use. Also requires sslkey', valueHelp: 'sslcert', ) ..addOption( 'sslkey', - help: - 'The key of the SSL certificate to use.\r\nIf set along with sslcert, ' - 'https will be used.\r\nSee the dart documentation about ' - 'SecurityContext.usePrivateKey for more.', + help: 'The key of the SSL certificate to use. Also requires sslcert', valueHelp: 'sslkey', ) ..addOption( 'sslkeypassword', - help: 'The password for the key of the SSL certificate to use.\r\nRequired ' - 'if the ssl key being used has a password set.\r\nSee the dart ' - 'documentation about SecurityContext.usePrivateKey for more.', + help: 'The password for the key of the SSL certificate to use.', valueHelp: 'sslkeypassword', ) ..addFlag( diff --git a/test/readme_test.dart b/test/readme_test.dart index 0b5f172..ef2180c 100644 --- a/test/readme_test.dart +++ b/test/readme_test.dart @@ -23,13 +23,16 @@ Future _readmeCheck(List args) async { expect(expected, r'''```console $ dhttpd --help --p, --port= The port to listen on. - (defaults to "8080") - --path= The path to serve. If not set, the current directory is used. - --headers= HTTP headers to apply to each response. header=value;header2=value - --host= The hostname to listen on. - (defaults to "localhost") --h, --help Displays the help. +-p, --port= The port to listen on. + (defaults to "8080") + --path= The path to serve. If not set, the current directory is used. + --headers= HTTP headers to apply to each response. header=value;header2=value + --host= The hostname to listen on. + (defaults to "localhost") + --sslcert= The SSL certificate to use. Also requires sslkey + --sslkey= The key of the SSL certificate to use. Also requires sslcert + --sslkeypassword= The password for the key of the SSL certificate to use. +-h, --help Displays the help. ```'''); expect(readme.readAsStringSync(), contains(expected)); From 8d448e3b48f86b288f9484893333185b4e3c9676 Mon Sep 17 00:00:00 2001 From: Maksim Lin Date: Thu, 4 Apr 2024 09:10:04 +1100 Subject: [PATCH 5/5] fix dart format --- bin/dhttpd.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bin/dhttpd.dart b/bin/dhttpd.dart index e5aef84..8a4cf34 100644 --- a/bin/dhttpd.dart +++ b/bin/dhttpd.dart @@ -30,8 +30,7 @@ Future main(List args) async { sslPassword: options.sslkeypassword, ); - print( - 'Server HTTP${httpd.isSSL ? 'S' : ''} started on port ${options.port}'); + print('Server HTTP${httpd.isSSL ? 'S' : ''} started on port ${options.port}'); } Map _parseKeyValuePairs(String str) => {