diff --git a/example/shelf_letsencrypt_example.dart b/example/shelf_letsencrypt_example.dart index ef3f9d2..c752d27 100644 --- a/example/shelf_letsencrypt_example.dart +++ b/example/shelf_letsencrypt_example.dart @@ -7,49 +7,55 @@ import 'package:shelf_letsencrypt/shelf_letsencrypt.dart'; late HttpServer server; // HTTP Server. late HttpServer serverSecure; // HTTPS Server. -/// Start the example with a list of domains and a recipriocal -/// email address for the domain admin. +/// Start the example with a list of domains and a reciprocal +/// e-mail address for the domain admin: /// ```dart -/// dart shelf_letsencxrypt_example.dart \ +/// dart shelf_letsencrypt_example.dart \ /// www.domain.com:www2.domain.com \ -/// info@domain.com:info@domain.com +/// info@domain.com:info2@domain.com /// ``` void main(List args) async { - final domainsArg = args[0]; // Domain for the HTTPS certificate. - final domainsEmailArg = args[1]; - var certificatesDirectory = - args.length > 2 ? args[2] : null; // Optional argument. + final domainNamesArg = args[0]; // Domain for the HTTPS certificate. + final domainEmailsArg = args[1]; // The domain e-mail. - certificatesDirectory ??= - '/tmp/shelf-letsencrypt-example/'; // Default directory. + var certificatesDirectory = args.length > 2 + ? args[2] // Optional argument. + : '/tmp/shelf-letsencrypt-example/'; // Default directory. - final domains = _extractDomainsFromArgs(domainsArg, domainsEmailArg); + final domains = + Domain.fromDomainsNamesAndEmailsArgs(domainNamesArg, domainEmailsArg); // The Certificate handler, storing at `certificatesDirectory`. final certificatesHandler = CertificatesHandlerIO(Directory(certificatesDirectory)); // The Let's Encrypt integration tool in `staging` mode: - final letsEncrypt = LetsEncrypt(certificatesHandler, - production: false, port: 80, securePort: 443); + final letsEncrypt = LetsEncrypt( + certificatesHandler, + production: false, + port: 80, + securePort: 443, + ); await _startServer(letsEncrypt, domains); + await _startRenewalService(letsEncrypt, domains, server, serverSecure); } Future _startServer(LetsEncrypt letsEncrypt, List domains) async { - // `shelf` Pipeline: + // Build `shelf` Pipeline: final pipeline = const Pipeline().addMiddleware(logRequests()); final handler = pipeline.addHandler(_processRequest); + // Start the HTTP and HTTPS servers: final servers = await letsEncrypt.startServer( handler, domains, loadAllHandledDomains: true, ); - server = servers[0]; // HTTP Server. - serverSecure = servers[1]; // HTTPS Server. + server = servers.http; // HTTP Server. + serverSecure = servers.https; // HTTPS Server. // Enable gzip: server.autoCompress = true; @@ -71,7 +77,7 @@ Future refreshIfRequired( LetsEncrypt letsEncrypt, List domains, ) async { - print('Checking if any certificates need to be renewed'); + print('-- Checking if any certificates need to be renewed'); var restartRequired = false; @@ -80,42 +86,19 @@ Future refreshIfRequired( await letsEncrypt.checkCertificate(domain, requestCertificate: true); if (result.isOkRefreshed) { - print('certificate for ${domain.name} was renewed'); + print('** Certificate for ${domain.name} was renewed'); restartRequired = true; } else { - print('Renewal not required'); + print('-- Renewal not required'); } } if (restartRequired) { - // restart the servers. + // Restart the servers: await Future.wait([server.close(), serverSecure.close()]); await _startServer(letsEncrypt, domains); - print('services restarted'); - } -} - -/// splits the command line arguments into a list of [Domain]s -/// containing the domain name and and domain email addresses. -List _extractDomainsFromArgs( - String domainsArg, String domainsEmailArg) { - final domainDelimiter = RegExp(r'\s*[;:,]\s*'); - final domainList = domainsArg.split(domainDelimiter); - final domainEmailList = domainsEmailArg.split(domainDelimiter); - - if (domainList.length != domainEmailList.length) { - stderr.writeln( - "The number of domains doesn't match the number of domain emails"); - exit(1); - } - - final domains = []; - - var i = 0; - for (final domain in domainList) { - domains.add(Domain(name: domain, email: domainEmailList[i++])); + print('** Services restarted'); } - return domains; } Response _processRequest(Request request) => diff --git a/lib/src/domain.dart b/lib/src/domain.dart index 4da272d..74637da 100644 --- a/lib/src/domain.dart +++ b/lib/src/domain.dart @@ -1,5 +1,40 @@ /// A [LetsEncrypt] domain. class Domain { + /// The default delimiter for [fromDomainsNamesAndEmailsArgs]. + static RegExp defaultArgDelimiter = RegExp(r'\s*[;:,]\s*'); + + /// Splits the arguments [domainNamesArg] and [domainEmailsArg] using the + /// specified [delimiter] pattern and creates a list of [Domain]s. + /// - If [delimiter] is not provided, the [defaultArgDelimiter] is used. + /// - See [fromDomainsNamesAndEmails]. + static List fromDomainsNamesAndEmailsArgs( + String domainNamesArg, String domainEmailsArg, + {RegExp? delimiter}) { + delimiter ??= defaultArgDelimiter; + + final domainNames = domainNamesArg.split(delimiter); + final domainEmails = domainEmailsArg.split(delimiter); + + return fromDomainsNamesAndEmails(domainNames, domainEmails); + } + + /// Generates a list of [Domain] instances from pairs in the sequence of + /// [domainNames] and [domainEmails]. + static List fromDomainsNamesAndEmails( + List domainNames, List domainEmails) { + if (domainNames.length != domainEmails.length) { + throw ArgumentError( + "The number of domain names (${domainNames.length}) doesn't match the number of domain emails (${domainEmails.length})."); + } + + var domains = List.generate( + domainNames.length, + (i) => Domain(name: domainNames[i], email: domainEmails[i]), + ); + + return domains; + } + /// The domain name. Ex.: your-domain.com final String name; @@ -21,5 +56,5 @@ class Domain { domains.any((domain) => domain.name == name); @override - String toString() => '$name : $email'; + String toString() => '$name: $email'; } diff --git a/lib/src/letsencrypt.dart b/lib/src/letsencrypt.dart index bd2fa19..30f7f29 100644 --- a/lib/src/letsencrypt.dart +++ b/lib/src/letsencrypt.dart @@ -279,13 +279,16 @@ class LetsEncrypt { for (var entry in domainsAndEmails.entries) { domains.add(Domain(name: entry.key, email: entry.value)); } - return startServer(handler, domains, + + var servers = await startServer(handler, domains, backlog: backlog, shared: shared, checkCertificate: checkCertificate, requestCertificate: requestCertificate, forceRequestCertificate: forceRequestCertificate, loadAllHandledDomains: loadAllHandledDomains); + + return [servers.http, servers.https]; } /// Starts 2 [HttpServer] instances, one HTTP at [port] @@ -293,12 +296,15 @@ class LetsEncrypt { /// /// - If [checkCertificate] is `true`, will check the current certificate /// - if [requestCertificate] is `true` then we will acquire/renew the certificate - /// as needed. + /// as needed. /// - If [forceRequestCertificate] is `true` then we will force the acquistion - /// of a new certificate. WARNING: the Lets Encrypt CA has VERY tight rate limits + /// of a new certificate. + /// + /// *WARNING: the Lets Encrypt CA has VERY tight rate limits /// on certificate acquistion. If you breach them you will not be able to - /// acquire a new production certificate for 168 hours!!!! - Future> startServer(Handler handler, List domains, + /// acquire a new production certificate for 168 hours!!!* + Future<({HttpServer http, HttpServer https})> startServer( + Handler handler, List domains, {int? backlog, bool shared = false, bool checkCertificate = true, @@ -306,7 +312,7 @@ class LetsEncrypt { bool forceRequestCertificate = false, bool loadAllHandledDomains = false}) async { logger.info( - '''Starting server> bindingAddress: $bindingAddress ; port: $port ; domain: $domains'''); + "Starting server> bindingAddress: $bindingAddress ; port: $port ; domain: $domains"); FutureOr handlerWithChallenge(Request r) { final path = r.requestedUri.path; @@ -411,7 +417,7 @@ class LetsEncrypt { } } - return [server, secureServer]; + return (http: server, https: secureServer); } /// Checks the [domain] certificate.