Repository navigation
Expand file tree
/
Copy pathhttp_cache.osc
More file actions
283 lines (266 loc) · 10.3 KB
/
Copy pathhttp_cache.osc
File metadata and controls
283 lines (266 loc) · 10.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
// http_cache.osc — in-memory LRU page cache for OscaWeb.
//
// Stores the HTML document body + content-type + status keyed by full URL.
// Used by the main navigation loop to short-circuit back/forward traversal
// and repeat visits. Images have a separate cache in browser.osc; this
// module only caches top-level documents.
//
// Storage is parallel arrays (Oscan has no self-referential structs and wants
// growing operations outside any arena scope). FIFO eviction when full drops
// the oldest entry from every parallel array. True LRU would
// require tracking access order which isn't worth the complexity for a
// 20-slot cache that is almost always dominated by back/forward hits.
//
// Usage (from browser.osc):
// let mut pc_urls: [str] = [];
// let mut pc_bodies: [str] = [];
// let mut pc_cts: [str] = [];
// let mut pc_status: [i32] = [];
// ...
// let hit: i32 = http_cache_lookup(pc_urls, url);
// if hit >= 0 {
// let resp: HttpResponse = http_cache_build(pc_bodies[hit], pc_cts[hit], pc_status[hit]);
// ...
// } else {
// let resp: HttpResponse = http_get(...);
// if resp.ok { http_cache_put(pc_urls, pc_bodies, pc_cts, pc_status, url, resp); };
// }
use "http.osc"
use "storage.osc"
// Persistent cache format (single flat file, one record per line group):
// U:<url>
// C:<content-type>
// S:<status_code>
// B:<hex-escaped body bytes>
// We hex-escape bodies so newlines/binary cannot break the line-based
// format. Only text/* content is persisted; binary resources (images,
// fonts) stay in-memory only.
fn http_cache_persist_file() -> str { "oscanweb_cache.txt" }
// Maximum number of entries in the page cache. 20 matches the rough size
// of a typical browsing session's back-stack; beyond that FIFO eviction
// kicks in.
fn http_cache_max() -> i32 { 20 }
// Linear scan for a URL. Returns the index of the entry, or -1 if absent.
// URLs are compared byte-for-byte — callers are responsible for passing
// the canonical (post-redirect) URL to both lookup and put.
fn http_cache_lookup(urls: [str], url: str) -> i32 {
array_index_of_str(urls, url)
}
// Reconstruct an HttpResponse for a cache hit. We always report 200 +
// ok=true so callers don't have to special-case cached entries; the
// original status is preserved via the stored `status` array should
// future callers need it.
fn http_cache_build(body: str, content_type: str, status: i32) -> HttpResponse {
let empty: [str] = [];
HttpResponse {
status_code: status,
content_type: content_type,
body: body,
redirect_url: "",
error: "",
ok: status >= 200 and status < 300,
set_cookie_lines: empty,
cache_control: "",
}
}
// Remove the oldest entry from every parallel array.
fn! http_cache_evict_oldest(urls: [str], bodies: [str], cts: [str], status: [i32]) {
let mut urls_mut: [str] = urls;
let mut bodies_mut: [str] = bodies;
let mut cts_mut: [str] = cts;
let mut status_mut: [i32] = status;
let n: i32 = len(urls);
if n <= 0 { return; };
array_remove_at(urls_mut, 0);
array_remove_at(bodies_mut, 0);
array_remove_at(cts_mut, 0);
array_remove_at(status_mut, 0);
}
// Insert (or replace) a URL's entry. If the URL already exists it is
// updated in place. Otherwise the entry is appended; if the cache was
// already at capacity the oldest entry is evicted first.
//
// IMPORTANT: this performs `push()` on long-lived arrays and so must NOT
// be called from inside an `arena { }` block (see Copilot instructions'
// arena notes in this repo).
fn! http_cache_put(urls: [str], bodies: [str], cts: [str], status: [i32],
url: str, resp: HttpResponse) {
let mut urls_mut: [str] = urls;
let mut bodies_mut: [str] = bodies;
let mut cts_mut: [str] = cts;
let mut status_mut: [i32] = status;
let existing: i32 = http_cache_lookup(urls, url);
if existing >= 0 {
bodies_mut[existing] = resp.body;
cts_mut[existing] = resp.content_type;
status_mut[existing] = resp.status_code;
return;
};
if len(urls) >= http_cache_max() {
http_cache_evict_oldest(urls, bodies, cts, status);
};
push(urls_mut, url);
push(bodies_mut, resp.body);
push(cts_mut, resp.content_type);
push(status_mut, resp.status_code);
}
// Clear the entire cache in place. Used on manual reload (`r`) so the
// user can force a re-fetch.
fn! http_cache_clear(urls: [str], bodies: [str], cts: [str], status: [i32]) {
let mut urls_mut: [str] = urls;
let mut bodies_mut: [str] = bodies;
let mut cts_mut: [str] = cts;
let mut status_mut: [i32] = status;
array_clear(urls_mut);
array_clear(bodies_mut);
array_clear(cts_mut);
array_clear(status_mut);
}
// Remove a single URL from the cache (used by the reload path to force a
// re-fetch of just the current page without discarding the rest).
fn! http_cache_invalidate(urls: [str], bodies: [str], cts: [str], status: [i32], url: str) {
let mut urls_mut: [str] = urls;
let mut bodies_mut: [str] = bodies;
let mut cts_mut: [str] = cts;
let mut status_mut: [i32] = status;
let idx: i32 = http_cache_lookup(urls, url);
if idx < 0 { return; };
array_remove_at(urls_mut, idx);
array_remove_at(bodies_mut, idx);
array_remove_at(cts_mut, idx);
array_remove_at(status_mut, idx);
}
// ── Persistent on-disk cache ────────────────────────────────
//
// We serialize the text/* entries to a single flat file in %APPDATA%
// (or $HOME) so repeat visits survive browser restarts. Binary bodies
// are skipped because the line-based format needs reversible escaping;
// a future revision could switch to a length-prefixed layout.
fn! http_cache_hex_nibble(n: i32) -> str {
if n < 10 { return "{n}"; };
if n == 10 { return "a"; };
if n == 11 { return "b"; };
if n == 12 { return "c"; };
if n == 13 { return "d"; };
if n == 14 { return "e"; };
"f"
}
fn! http_cache_hex_val(c: i32) -> i32 {
if c >= 48 and c <= 57 { return c - 48; };
if c >= 97 and c <= 102 { return c - 97 + 10; };
if c >= 65 and c <= 70 { return c - 65 + 10; };
-1
}
fn! http_cache_hex_encode(body: str) -> str {
// Build the output as a char array and join once. Earlier versions
// used `out = str_concat(out, ...)` per byte, which is O(n^2) and
// froze the browser for tens of seconds (or longer) on quit when
// any sizeable text resource (Wikipedia article, large JS bundle)
// was in the cache.
let chars: [i32] = str_to_chars(body);
let n: i32 = len(chars);
let mut out_chars: [i32] = [];
let mut i: i32 = 0;
while i < n {
let b: i32 = chars[i] % 256;
let hi: i32 = (b / 16) % 16;
let lo: i32 = b % 16;
let hi_c: i32 = if hi < 10 { 48 + hi } else { 97 + (hi - 10) };
let lo_c: i32 = if lo < 10 { 48 + lo } else { 97 + (lo - 10) };
push(out_chars, hi_c);
push(out_chars, lo_c);
i += 1;
};
str_from_chars(out_chars)
}
fn! http_cache_hex_decode(hex: str) -> str {
let chars: [i32] = str_to_chars(hex);
let n: i32 = len(chars);
let mut out_chars: [i32] = [];
let mut i: i32 = 0;
while i + 1 < n {
let hi: i32 = http_cache_hex_val(chars[i]);
let lo: i32 = http_cache_hex_val(chars[i + 1]);
if hi < 0 or lo < 0 { return str_from_chars(out_chars); };
push(out_chars, hi * 16 + lo);
i += 2;
};
str_from_chars(out_chars)
}
fn! http_cache_is_text(ct: str) -> bool {
let lc: str = str_to_lower(ct);
str_find(lc, "text/") >= 0 or
str_find(lc, "javascript") >= 0 or
str_find(lc, "json") >= 0 or
str_find(lc, "xml") >= 0 or
str_find(lc, "css") >= 0
}
// Serialize the text/* entries to the persistent cache file.
// Called from browser.osc on a graceful quit; a crash mid-save simply
// leaves the prior file untouched since write_file is atomic-at-replace.
fn! http_cache_save(urls: [str], bodies: [str], cts: [str], status: [i32]) {
let mut lines: [str] = [];
let n: i32 = len(urls);
let mut i: i32 = 0;
while i < n {
if http_cache_is_text(cts[i]) {
push(lines, str_concat("U:", urls[i]));
push(lines, str_concat("C:", cts[i]));
push(lines, str_concat("S:", "{status[i]}"));
push(lines, str_concat("B:", http_cache_hex_encode(bodies[i])));
};
i += 1;
};
storage_save_lines(http_cache_persist_file(), lines);
}
// Load previously-saved cache entries into the in-memory arrays.
// Silently no-ops on any format error. Called once at startup.
fn! http_cache_load(urls: [str], bodies: [str], cts: [str], status: [i32]) {
let mut urls_mut: [str] = urls;
let mut bodies_mut: [str] = bodies;
let mut cts_mut: [str] = cts;
let mut status_mut: [i32] = status;
let lines: [str] = storage_load_lines(http_cache_persist_file());
let n: i32 = len(lines);
let mut i: i32 = 0;
let mut cur_url: str = "";
let mut cur_ct: str = "";
let mut cur_status: i32 = 200;
let mut have_url: bool = false;
let mut have_ct: bool = false;
let mut have_st: bool = false;
while i < n {
let line: str = lines[i];
if str_len(line) < 2 {
i += 1;
continue;
};
let tag: str = str_slice(line, 0, 2);
let rest: str = str_slice(line, 2, str_len(line));
if str_eq(tag, "U:") {
cur_url = rest;
have_url = true;
} else if str_eq(tag, "C:") {
cur_ct = rest;
have_ct = true;
} else if str_eq(tag, "S:") {
let p_st: Result<i32, str> = parse_i32(rest);
match p_st {
Result::Ok(v) => { cur_status = v; have_st = true; },
Result::Err(_e) => { cur_status = 200; have_st = true; },
};
} else if str_eq(tag, "B:") and have_url and have_ct and have_st {
if len(urls) >= http_cache_max() {
http_cache_evict_oldest(urls, bodies, cts, status);
};
push(urls_mut, cur_url);
push(bodies_mut, http_cache_hex_decode(rest));
push(cts_mut, cur_ct);
push(status_mut, cur_status);
have_url = false;
have_ct = false;
have_st = false;
};
i += 1;
};
}