From 90abfbf79f1f49d2c448b9c01bd7755ae8355534 Mon Sep 17 00:00:00 2001 From: Emil Lerch Date: Mon, 26 Oct 2026 15:27:48 -0700 Subject: [PATCH] create new http client for Ipwhois --- .forgejo/workflows/build.yaml | 2 +- src/location/Ip2location.zig | 11 +++++++---- src/location/IpWhoIs.zig | 16 ++++++++++++---- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/.forgejo/workflows/build.yaml b/.forgejo/workflows/build.yaml index 06521b4..faa5a27 100644 --- a/.forgejo/workflows/build.yaml +++ b/.forgejo/workflows/build.yaml @@ -15,7 +15,7 @@ jobs: - name: Check out repository code uses: actions/checkout@v4 - name: Setup Zig - uses: https://codeberg.org/mlugg/setup-zig@v2.2.1 + uses: https://github.com/mlugg/setup-zig@v2.2.1 - name: Build project run: zig build --summary all - name: Run tests diff --git a/src/location/Ip2location.zig b/src/location/Ip2location.zig index d332358..6745b2f 100644 --- a/src/location/Ip2location.zig +++ b/src/location/Ip2location.zig @@ -13,7 +13,6 @@ allocator: Allocator, /// every method that touches a file or the network. io: std.Io, api_key: ?[]const u8, -http_client: std.http.Client, cache: *Cache, pub fn init(allocator: Allocator, io: std.Io, api_key: ?[]const u8, cache_path: []const u8) !Self { @@ -24,7 +23,6 @@ pub fn init(allocator: Allocator, io: std.Io, api_key: ?[]const u8, cache_path: .allocator = allocator, .io = io, .api_key = if (api_key) |k| try allocator.dupe(u8, k) else null, - .http_client = std.http.Client{ .allocator = allocator, .io = io }, .cache = cache, }; } @@ -32,7 +30,6 @@ pub fn init(allocator: Allocator, io: std.Io, api_key: ?[]const u8, cache_path: pub fn deinit(self: *Self) void { self.cache.deinit(); self.allocator.destroy(self.cache); - self.http_client.deinit(); if (self.api_key) |k| self.allocator.free(k); } @@ -106,9 +103,15 @@ fn fetch(self: *Self, ip_str: []const u8) !Location { if (self.api_key) |key| try w.print("&key={s}", .{key}); + // A fresh client per request, deliberately: a long-lived `std.http.Client` + // validates certificates against the time of its first request. See the + // matching comment in `IpWhoIs.fetch` for the failure this prevents. + var client: std.http.Client = .{ .allocator = self.allocator, .io = self.io }; + defer client.deinit(); + var response_buf: [4096]u8 = undefined; var writer = std.Io.Writer.fixed(&response_buf); - const result = try self.http_client.fetch(.{ + const result = try client.fetch(.{ .location = .{ .url = w.buffered() }, .method = .GET, .response_writer = &writer, diff --git a/src/location/IpWhoIs.zig b/src/location/IpWhoIs.zig index 9f4988e..ed23d42 100644 --- a/src/location/IpWhoIs.zig +++ b/src/location/IpWhoIs.zig @@ -11,7 +11,6 @@ const log = std.log.scoped(.ipwhois); allocator: Allocator, /// Zig 0.16 requires an explicit `Io` for filesystem and HTTP work. io: std.Io, -http_client: std.http.Client, cache: *Cache, pub fn init(allocator: Allocator, io: std.Io, cache_path: []const u8) !Self { @@ -21,7 +20,6 @@ pub fn init(allocator: Allocator, io: std.Io, cache_path: []const u8) !Self { return .{ .allocator = allocator, .io = io, - .http_client = std.http.Client{ .allocator = allocator, .io = io }, .cache = cache, }; } @@ -29,7 +27,6 @@ pub fn init(allocator: Allocator, io: std.Io, cache_path: []const u8) !Self { pub fn deinit(self: *Self) void { self.cache.deinit(); self.allocator.destroy(self.cache); - self.http_client.deinit(); } pub fn lookup(self: *Self, ip_str: []const u8) ?Location { @@ -69,9 +66,20 @@ fn fetch(self: *Self, ip_str: []const u8) !Location { // which cannot be derived from `country` without matching display text. try w.writeAll("?fields=city,region,country,country_code,latitude,longitude&output=json"); + // A fresh client per request, deliberately. `std.http.Client` latches the + // wall clock into `now` on its first HTTPS request and validates every + // later certificate against that frozen time. A client kept for the life of + // the server therefore rejects any certificate issued after its first + // lookup (`CertificateNotYetValid`, which std reports only as + // `TlsInitializationFailed`), so lookups start failing as soon as the + // provider rotates its certificate and keep failing until a restart. + // Lookups are cached permanently, so the per-request CA bundle scan is rare. + var client: std.http.Client = .{ .allocator = self.allocator, .io = self.io }; + defer client.deinit(); + var response_buf: [4096]u8 = undefined; var writer = std.Io.Writer.fixed(&response_buf); - const result = try self.http_client.fetch(.{ + const result = try client.fetch(.{ .location = .{ .url = w.buffered() }, .method = .GET, .response_writer = &writer,