Repository navigation
Reuse HTTPS connections, negotiate HTTP/2, add new response fields - #19
Merged
Merged
Conversation
Both Feign proxies behind an IpdataService (the full-model/bulk client and the single-field client) were built with their own feign.Client.Default, i.e. HttpURLConnection, so every lookup paid for its own connection setup and TLS handshake. They now share one transport: feign-java11's Http2Client over the JDK java.net.http client, which keeps a single connection pool for the lifetime of the service, multiplexes requests over HTTP/2 when the server negotiates it and falls back to HTTP/1.1 keep-alive otherwise. The same feign.Request.Options instance is handed to Http2Client and to both Feign builders: Http2Client silently builds a new HttpClient (and so a new connection) for every request whose options differ from the ones it was constructed with. Timeouts are configurable through the builder (connectTimeout/readTimeout or options(Request.Options)); the existing IpdataServiceBuilder.of(url, key, cacheConfig, logger, client) factory is kept and an injected feignClient keeps working, shared by all calls of the service. Tests: MockIpdataServer records the client socket address and protocol of every request; ConnectionReuseTest proves that sequential full-model, single-field, selected-fields and bulk calls arrive on one connection (with default and custom options), that an injected client serves both proxies, that the builder options reach the transport on every call and that the default transport works against the plain-HTTP mock (h2c upgrade attempt, HTTP/1.1 fallback). Version 0.3.0 (pom version, scm tag, README install snippet); the README gains a "Connection reuse and HTTP/2" section. Claude-Session: https://claude.ai/code/session_01VQiRp4zEJKUXHrKkFaeUke Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The API now flags satellite ISP addresses (is_satellite) and anycast
addresses (is_anycast) next to is_eu, sending each key only when it is
true, and every threat.blocklists entry may carry first_seen and
last_seen (YYYY-MM-DD, UTC) and days_seen; some feeds, such as ipdata's
own vpn feed, omit all three.
IpdataModel gains primitive booleans mapped like is_eu (isSatellite(),
isAnycast()) that read false when the key is absent. Blocklist gains
nullable String firstSeen/lastSeen and Integer daysSeen; the dates stay
strings (no new dependency, no format coupling). IpdataField gains
IS_SATELLITE and IS_ANYCAST for getFields(...), and the single-field
service methods isSatellite(ip) and isAnycast(ip) follow the existing
GET /{ip}/{field} pattern. The Jackson decoder already ignores unknown
properties (FAIL_ON_UNKNOWN_PROPERTIES is false); a test now covers it.
Tests: fixtures built from the API samples (98.97.34.0 satellite ISP,
8.8.8.8 extended with is_anycast and two dated blocklist entries,
187.13.250.1 VPN exit whose third blocklist entry has no dates);
NewResponseFieldsTest asserts the typed values, the false-when-absent
behaviour, the single-field endpoints, field selection with the new
names, bulk responses and unknown-key tolerance, with and without the
cache. The README documents the five fields and the new methods.
Claude-Session: https://claude.ai/code/session_01VQiRp4zEJKUXHrKkFaeUke
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or 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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
IpdataService(the full-model/bulk client and the single-field client) used to be built with their ownfeign.Client.Default(HttpURLConnection), so every lookup paid for its own connection and TLS handshake. They now share one transport: feign-java11'sHttp2Clientover the JDKjava.net.http.HttpClient, which keeps a single connection pool for the lifetime of the service, multiplexes requests over HTTP/2 when the server negotiates it (ALPN) and falls back to HTTP/1.1 keep-alive otherwise.feign.Request.Optionsinstance is handed toHttp2Clientand to both Feign builders. This pairing is load-bearing:Http2Client11.10 silently builds a newHttpClient(and so a new connection) for every request whose options differ from the ones it was constructed with.ConnectionReuseTestguards it with non-default timeouts.options(Request.Options),connectTimeout(long, TimeUnit),readTimeout(long, TimeUnit);IpdataServiceBuilder.of(url, key, cacheConfig, logger, client, options)next to the unchanged 5-argument overload;feignClient(...)injection kept, the injected client is shared by all calls.is_satellite/is_anycast(IpdataModel.isSatellite()/isAnycast(), primitive booleans that readfalsewhen the API omits the key),Blocklist.getFirstSeen()/getLastSeen()/getDaysSeen()(nullableString,String,Integer),IpdataField.IS_SATELLITE/IS_ANYCAST, and the single-field service methodsisSatellite(ip)/isAnycast(ip).ConnectionReuseTest(14 tests) runs through the existingMockIpdataServer, which now records the client socket address and protocol of every request. Sequential full-model, single-field, selected-fields, bulk (POST) and IPv6 calls on one service arrive from the same client port, with default options, withconnectTimeout/readTimeoutand with an explicitRequest.Options; a countingfeign.Clientinjected viafeignClient(...)serves all calls of both proxies (plain and caching service); the builder options reach the transport on every call; a default-built service works against the plain-HTTP mock (HTTP/1.1after the h2c upgrade attempt).NewResponseFieldsTest(9 tests) covers the new fields. No test calls the live API.io.github.openfeign:feign-java11:11.10(same Feign version as the other modules). Java 11 minimum unchanged. CI already runs on pull requests; no workflow change.Two logical commits:
3685a51connection reuse / HTTP/2,6d1d95cnew response fields.What changes for users
IpdataServiceno longer pay a TCP + TLS handshake. Create one service per process (or per API key / endpoint), keep it and reuse it for every call; it is thread-safe. There is nothing to close: the JDK 11HttpClienthas no close method and drops idle connections itself. It exposes no per-instance idle timeout, HTTP/2 ping or TCP keepalive setting, so the JDK defaults apply; thejdk.httpclient.keepalive.timeoutsystem property (1200 s by default) governs idle connections.java.net.http.HttpClientinstead ofHttpURLConnection: proxies follow the standardhttps.proxyHost/http.nonProxyHostsproperties throughProxySelector.getDefault()as before; TLS uses the defaultSSLContext(javax.net.ssl.*properties). Code that relied onHttpsURLConnection.setDefaultSSLSocketFactory/setDefaultHostnameVerifiershould inject afeignClient(Apache HttpClient viafeign-httpclient, OkHttp viafeign-okhttp). Response bodies are read fully before decoding (they are small JSON documents). Against a plainhttp://endpoint the JDK client attempts anh2cupgrade and falls back to HTTP/1.1 (tested against the mock server).RemoteIpdataException/RateLimitExceptionand are never retried; a transportIOException(for example on a pooled connection the server had already closed, before any response byte) is still re-sent by Feign's defaultRetryer, exactly as before, so no second retry layer was added.User-Agent(io.ipdata.client.java.<version>) andAccept: application/jsonunchanged.model.isSatellite(),model.isAnycast(),blocklist.getFirstSeen(),getLastSeen()(YYYY-MM-DD, UTC, kept as strings),getDaysSeen();getFields(ip, IpdataField.IS_SATELLITE, IpdataField.IS_ANYCAST);ipdataService.isSatellite(ip)/isAnycast(ip). The decoder ignores unknown keys (FAIL_ON_UNKNOWN_PROPERTIESwas alreadyfalse; now covered by a test).Release notes
Tag to push after merge:
v0.3.0(theReleaseworkflow triggers onv*tags and deploys to Maven Central). Note: the existing tagsv0.2.1andv0.2.2both point at pom version 0.2.1, and Maven Central has no 0.2.2; 0.3.0 is the next published version.Verification
Local builds with JDK 11, Maven wrapper 3.6.3 and a UTF-8 locale (
LC_ALL=C.UTF-8 LANG=C.UTF-8, see "Noticed, not changed"):origin/master(cda239d):./mvnw -B -Dmaven.artifact.threads=1 clean test post-integration-test→Tests run: 57, Failures: 0, Errors: 0, Skipped: 0, BUILD SUCCESS.3685a51): same command →Tests run: 71, Failures: 0, Errors: 0, Skipped: 0(14 new tests inConnectionReuseTest), BUILD SUCCESS. The lifecycle runspackage, so the main, sources and javadoc jars (ipdata-java-client-0.3.0*.jar) were built; no Javadoc warnings.6d1d95c, the pushed HEAD): same command →Tests run: 80, Failures: 0, Errors: 0, Skipped: 0(9 new tests inNewResponseFieldsTest), BUILD SUCCESS. This is the CI command (mvn clean test post-integration-test, temurin 11).Two sequential requests from an
IpdataServiceto api.ipdata.co were served over HTTP/2 and the second reused the connection.CI: the
CIworkflow (.github/workflows/ci.yml,pull_requesttrigger formaster) ran on this PR for6d1d95c: run #10, https://github.com/ipdata/java/actions/runs/41621426785, jobtestsuccess (mvn clean test post-integration-teston temurin 11). No follow-up commits were needed.Noticed, not changed
TestContext.get()/post()andMockIpdataServer.handleBulk()decode HTTP bodies with the platform default charset (new InputStreamReader(stream)without a charset): on JDK 11 with a POSIX locale 10 tests fail on emoji / currency strings, and pass withLC_ALL=C.UTF-8. Pre-existing, test-only.languages[].codebut theLanguagemodel has nocodefield; the lenient project mapper drops it, the strict test mapper would reject a fixture containing it.Ipv6SafeClientuses the deprecatedRequest.create(HttpMethod, String, Map, byte[], Charset)(deprecation warning at compile time); it still works with Feign 11.10.is_vpn,blocklistsandscores; the table of contents links "API" to#create-an-instance, an anchor that does not exist; "Bulk data" is a###heading although it belongs to the API section.IpdataServiceSupport.getSingleFieldClient()returns the full-model client andgetApi()the single-field client (names swapped; harmless, both are Lombok delegates).ScoresandBlocklistuse plaingetX()getters while every other model uses fluent accessors (@Accessors(fluent = true)); the newBlocklistgetters follow the class they live in..mvn/extensions.xmldeclares thepolyglot-groovy0.4.3 build extension although the project uses a plainpom.xml; it is downloaded on every fresh build and never used.feign-java11.🤖 Generated with Claude Code