Skip to content

Commit

Permalink
- Fixed a typo in the comment by changing "recipriocal" to "reciprocal".
Browse files Browse the repository at this point in the history
- Improved argument processing in the example by using conditional assignment and added explicit type annotations to improve readability.
- Updated the `fromDomainsNamesAndEmailsArgs` method to split arguments using a specified delimiter and provide a list of `Domain` instances.
- Updated the `startServer` method to return a structured object with `http` and `https` servers instead of a list of `HttpServer`s.
  • Loading branch information
gmpassos committed Feb 3, 2024
1 parent b962843 commit 1dd774e
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 52 deletions.
71 changes: 27 additions & 44 deletions example/shelf_letsencrypt_example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> 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<void> _startServer(LetsEncrypt letsEncrypt, List<Domain> 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;
Expand All @@ -71,7 +77,7 @@ Future<void> refreshIfRequired(
LetsEncrypt letsEncrypt,
List<Domain> domains,
) async {
print('Checking if any certificates need to be renewed');
print('-- Checking if any certificates need to be renewed');

var restartRequired = false;

Expand All @@ -80,42 +86,19 @@ Future<void> 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<void>([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<Domain> _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 = <Domain>[];

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) =>
Expand Down
37 changes: 36 additions & 1 deletion lib/src/domain.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,40 @@
/// A [LetsEncrypt] domain.
class Domain {
/// The default delimiter for [fromDomainsNamesAndEmailsArgs].
static RegExp defaultArgDelimiter = RegExp(r'\s*[;:,]\s*');

Check warning on line 4 in lib/src/domain.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/domain.dart#L4

Added line #L4 was not covered by tests

/// 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<Domain> fromDomainsNamesAndEmailsArgs(

Check warning on line 10 in lib/src/domain.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/domain.dart#L10

Added line #L10 was not covered by tests
String domainNamesArg, String domainEmailsArg,
{RegExp? delimiter}) {
delimiter ??= defaultArgDelimiter;

Check warning on line 13 in lib/src/domain.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/domain.dart#L13

Added line #L13 was not covered by tests

final domainNames = domainNamesArg.split(delimiter);
final domainEmails = domainEmailsArg.split(delimiter);

Check warning on line 16 in lib/src/domain.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/domain.dart#L15-L16

Added lines #L15 - L16 were not covered by tests

return fromDomainsNamesAndEmails(domainNames, domainEmails);

Check warning on line 18 in lib/src/domain.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/domain.dart#L18

Added line #L18 was not covered by tests
}

/// Generates a list of [Domain] instances from pairs in the sequence of
/// [domainNames] and [domainEmails].
static List<Domain> fromDomainsNamesAndEmails(

Check warning on line 23 in lib/src/domain.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/domain.dart#L23

Added line #L23 was not covered by tests
List<String> domainNames, List<String> 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}).");

Check warning on line 27 in lib/src/domain.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/domain.dart#L25-L27

Added lines #L25 - L27 were not covered by tests
}

var domains = List.generate(
domainNames.length,
(i) => Domain(name: domainNames[i], email: domainEmails[i]),

Check warning on line 32 in lib/src/domain.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/domain.dart#L30-L32

Added lines #L30 - L32 were not covered by tests
);

return domains;
}

/// The domain name. Ex.: your-domain.com
final String name;

Expand All @@ -21,5 +56,5 @@ class Domain {
domains.any((domain) => domain.name == name);

@override
String toString() => '$name : $email';
String toString() => '$name: $email';
}
20 changes: 13 additions & 7 deletions lib/src/letsencrypt.dart
Original file line number Diff line number Diff line change
Expand Up @@ -279,34 +279,40 @@ 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,

Check warning on line 283 in lib/src/letsencrypt.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/letsencrypt.dart#L283

Added line #L283 was not covered by tests
backlog: backlog,
shared: shared,
checkCertificate: checkCertificate,
requestCertificate: requestCertificate,
forceRequestCertificate: forceRequestCertificate,
loadAllHandledDomains: loadAllHandledDomains);

return [servers.http, servers.https];

Check warning on line 291 in lib/src/letsencrypt.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/letsencrypt.dart#L291

Added line #L291 was not covered by tests
}

/// Starts 2 [HttpServer] instances, one HTTP at [port]
/// and other HTTPS at [securePort].
///
/// - 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<List<HttpServer>> startServer(Handler handler, List<Domain> domains,
/// acquire a new production certificate for 168 hours!!!*
Future<({HttpServer http, HttpServer https})> startServer(
Handler handler, List<Domain> domains,
{int? backlog,
bool shared = false,
bool checkCertificate = true,
bool requestCertificate = true,
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<Response> handlerWithChallenge(Request r) {
final path = r.requestedUri.path;
Expand Down Expand Up @@ -411,7 +417,7 @@ class LetsEncrypt {
}
}

return [server, secureServer];
return (http: server, https: secureServer);
}

/// Checks the [domain] certificate.
Expand Down

0 comments on commit 1dd774e

Please sign in to comment.