Compare commits

..
2 Commits
Author SHA1 Message Date
XoséandClaude Opus 5.5 bdf8de2767 Http.abort: leave responses that were read to the end alone
Their connection is back in the pool and may be carrying another request by then (in Plexamp Lite this cut
read-ahead downloads short). From Plexamp Lite 1.14.1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 19:09:54 +01:00
XoséandClaude Opus 5.5 3fa210fc60 Add spec.md: goal, limits and rules for working on the project
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 09:05:38 +01:00
2 changed files with 73 additions and 16 deletions

No files matched your search

+32 -16
View File
@@ -451,19 +451,25 @@ public final class Http {
// ------------------------------------------------------------------ body streams // ------------------------------------------------------------------ body streams
private static final class FixedStream extends InputStream { private static final class FixedStream extends InputStream {
private final Conn c; private long left; private final boolean keepAlive; private boolean closed; private final Conn c; private long left; private final boolean keepAlive; private volatile boolean closed;
FixedStream(Conn c, long len, boolean keepAlive) { FixedStream(Conn c, long len, boolean keepAlive) {
this.c = c; this.left = len; this.keepAlive = keepAlive; this.c = c; this.left = len; this.keepAlive = keepAlive;
if (len == 0) finish(true); if (len == 0) finish(true);
} }
private void finish(boolean complete) { private synchronized void finish(boolean complete) {
if (closed) return; if (closed) return;
closed = true; closed = true;
if (complete && keepAlive) release(c); else c.close(); if (complete && keepAlive) release(c); else c.close();
} }
synchronized void abort() {
if (closed) return; // done: the connection may already be back in the pool, serving someone else
closed = true;
try { c.socket.close(); } catch (Exception ignored) {}
}
@Override public int read() throws IOException { @Override public int read() throws IOException {
byte[] one = new byte[1]; byte[] one = new byte[1];
int n = read(one, 0, 1); int n = read(one, 0, 1);
@@ -490,16 +496,22 @@ public final class Http {
private static final class ChunkedStream extends InputStream { private static final class ChunkedStream extends InputStream {
private final Conn c; private final boolean keepAlive; private final Conn c; private final boolean keepAlive;
private long chunkLeft = 0; private boolean eof; private boolean closed; private long chunkLeft = 0; private boolean eof; private volatile boolean closed;
ChunkedStream(Conn c, boolean keepAlive) { this.c = c; this.keepAlive = keepAlive; } ChunkedStream(Conn c, boolean keepAlive) { this.c = c; this.keepAlive = keepAlive; }
private void finish(boolean complete) { private synchronized void finish(boolean complete) {
if (closed) return; if (closed) return;
closed = true; closed = true;
if (complete && keepAlive) release(c); else c.close(); if (complete && keepAlive) release(c); else c.close();
} }
synchronized void abort() {
if (closed) return; // done: the connection may already be back in the pool, serving someone else
closed = true;
try { c.socket.close(); } catch (Exception ignored) {}
}
private boolean nextChunk() throws IOException { private boolean nextChunk() throws IOException {
if (eof) return false; if (eof) return false;
String line = readLine(c.in); String line = readLine(c.in);
@@ -551,7 +563,7 @@ public final class Http {
} }
private static final class EofStream extends InputStream { private static final class EofStream extends InputStream {
private final Conn c; private boolean closed; private final Conn c; private volatile boolean closed;
EofStream(Conn c) { this.c = c; } EofStream(Conn c) { this.c = c; }
@Override public int read() throws IOException { @Override public int read() throws IOException {
@@ -568,21 +580,25 @@ public final class Http {
return n; return n;
} }
@Override public void close() { @Override public synchronized void close() {
if (!closed) { closed = true; c.close(); } if (!closed) { closed = true; c.close(); }
} }
}
/** Close one in-flight response's socket from another thread (used to abort streams). */ synchronized void abort() {
public static void abort(Response r) { if (closed) return;
if (r == null) return; closed = true;
InputStream b = r.body;
Conn c = null;
if (b instanceof FixedStream) c = ((FixedStream) b).c;
else if (b instanceof ChunkedStream) c = ((ChunkedStream) b).c;
else if (b instanceof EofStream) c = ((EofStream) b).c;
if (c != null) {
try { c.socket.close(); } catch (Exception ignored) {} try { c.socket.close(); } catch (Exception ignored) {}
} }
} }
/** Close one in-flight response's socket from another thread (used to abort streams). A response that has
* already been read to the end is left alone: its connection went back to the pool and may be carrying
* another request by now (closing it cut read-ahead downloads short: "Unexpected end of stream"). */
public static void abort(Response r) {
if (r == null) return;
InputStream b = r.body;
if (b instanceof FixedStream) ((FixedStream) b).abort();
else if (b instanceof ChunkedStream) ((ChunkedStream) b).abort();
else if (b instanceof EofStream) ((EofStream) b).abort();
}
} }
+41
View File
@@ -0,0 +1,41 @@
# tlskit — spec
The short contract for working on the shared HTTP/TLS client. `README.md` explains its use; this file says
what it promises to the apps that copy it.
## Goal
Let Android 2.2/2.3 apps reach modern `https://` servers (TLS 1.2, today's roots, SNI), which the
system's own TLS 1.0 stack can't.
## Users
Copied into: Plexamp Lite, Podlite, Noticias Lite, Calendar Lite (by hand, under
`android/org/gingercal/net`), AcasaLite, Tenda Lite and WikipeLite. The apps carry copies, not a dependency.
## What it provides
- `net/Http.java`: HTTP/1.1 on Bouncy Castle TLS 1.2 (renamed `com.minamp.bc`), keep-alive pooling, session
resumption, chunked bodies.
- `net/CertVerifier.java`: host name, validity dates, chain up to a bundled Mozilla root (trust anchors must
be CAs); optionally one pinned leaf certificate (SHA-256), with the fingerprint shown to the user. Untrusted
errors carry the chain.
- `net/AssetRoots.java` + `roots/`: Mozilla's roots as `assets/roots/<sha1(subject)>-<n>.der`; loaded roots
are kept in memory and a failed read is retried.
- `net/Threads.java`: threads with a 256 KB stack; network work must run on one.
- `net/DiagLog.java`: connection log with tokens removed from URLs.
## Must not break
- Must run on Dalvik for API 8: no newer Java or Android APIs.
- Must not clash with the old Bouncy Castle inside Android (keep the `com.minamp.bc` rename).
- Never log tokens or passwords.
- A change here is not done until it has been reinstalled into every app above and each one rebuilt
(and released through Tenda).
## Workflow
- `./install.sh <app dir> <java package>` copies `net/` into the app's package and `roots/` into its `assets/`.
- Bouncy Castle sources come from Plexamp Lite's `tools/setup_toolchain.sh` (`~/android-toolchain/bcsrc`);
roots are regenerated with Plexamp Lite's `tools/make_roots.py`.
- Test: `test/KitTest.java` on a desktop JVM against real FreshRSS, Nextcloud and Audiobookshelf servers.