Skip to content

Commit dc6efe2

Browse files
committed
fix: cache now can correctly discard old version
1 parent 5aa30e5 commit dc6efe2

File tree

13 files changed

+179
-87
lines changed

13 files changed

+179
-87
lines changed

docs/develop/change-log.md

+31-10
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,28 @@
11
# Change Log
2-
### Version 2.1.0(client:2.0.1)
2+
3+
### Version 2.1.1
4+
5+
- [ ] fix:
6+
7+
### Version 2.1.0
8+
39
- [x] fix: upload file api is not correct.
410
- [x] fix: command line compile successfully.
5-
- [x] feat: add `_version` api to get domain current version.
6-
- [ ] feat: support serving multiple spa in one domain.
11+
- [x] feat: support serving multiple spa in one domain.
712
- [x] chore: js client sdk add node.js version restriction.
8-
- [x] chore: it's now used in production environment!
13+
- [x] chore: it's now used in production environment!
914

1015
### Version 2.0.0(client:2.0.0)
16+
1117
- [x] move project from timzaak to ForNet
1218
- [x] upgrade warp and other dependencies
1319
- [x] upgrade vitePress to 1.0.1
1420
- [x] change js sdk implementation from Rust to typescript
1521
- [x] doc: rewrite to support new doc
1622
- [x] cicd: remove client docker, server s3 docker.
1723

18-
1924
### Version v1.2.6(client:v0.1.4)
25+
2026
- [x] chore(break change): change server docker config and binary location
2127
- [x] feat: add cron job to delete deprecated version
2228
- [x] feat(with client): delete deprecated version to save storage
@@ -26,16 +32,19 @@
2632
- [x] doc: add algolia search, thanks for algolia company!
2733

2834
### Version 1.2.5(client v0.1.3)
35+
2936
- [x] build: add docker image cache for (spa-client|spa-server)-docker-cd.yml to speed cd process
3037
- [x] doc: use VitePress to rebuild docs, ready to get the world known it
3138
- [x] build: add CD for doc release
3239
- [x] feat: support multiple config for different domain (break change for config file)
3340
- [x] feat: support multiple ssl
34-
- [ ] ~~fix: disable put online domain which does not have correct ssl in server when https opened.~~(need to confirm if it's a bug?)
41+
- [ ] ~~fix: disable put online domain which does not have correct ssl in server when https opened.~~(need to confirm if
42+
it's a bug?)
3543
- [x] fix: fix wrong check when release new domain
3644
- [x] fix(js-client): npm package error
3745

3846
### Version 1.2.4(client:v0.1.1)
47+
3948
- [x] release commandline of spa-client for mac/ios/linux (by GitHub Actions), put them with GitHub release page
4049
- [x] fix possible bugs about uploading and spa-client(-js)
4150
- [x] build: release docker image by GitHubActions
@@ -44,44 +53,56 @@
4453
- [x] improve: add debug log for spa-server request
4554

4655
### version 1.2.3(client:v0.1.0)
56+
4757
- [x] admin server export http api to accept files to local file system
4858
- [x] add client to sync local files to admin server(retry support)
49-
- [ ] ~~release server/client to crate~~ [crate needs dep version, need replace warp firstly](https://github.com/rust-lang/cargo/issues/1565)
59+
- [ ] ~~release server/client to
60+
crate~~ [crate needs dep version, need replace warp firstly](https://github.com/rust-lang/cargo/issues/1565)
5061
- [x] doc about how to use with shell client
62+
5163
#### add js plugin
64+
5265
- [x] add js wrapper for spa-client
5366
- [x] and example/test frontend repo
5467
- [x] doc about how to use with js client
5568
- [x] release js wrapper to npm.org
5669

5770
### version 1.2.2
71+
5872
- [x] cache File `Range` Header support
59-
- [ ] ~~drop self maintained `Warp`(copy out needed code from Warp)~~ (so much code from warp/fs, I give up after try, will wait Warp release proper version)
73+
- [ ] ~~drop self maintained `Warp`(copy out needed code from Warp)~~ (so much code from warp/fs, I give up after try,
74+
will wait Warp release proper version)
6075
- [x] `HEAD` request support or drop(support, don't need to do anything)
6176

6277
### version 1.2.1
78+
6379
- [x] more log for debug and trace
6480
- [x] basic CORS
65-
- [x] compress regression support(~~if client don't send accept-encoding header(including gzip), will send back data from file instead of cache~~ improved by v1.2.3)
81+
- [x] compress regression support(~~if client don't send accept-encoding header(including gzip), will send back data
82+
from file instead of cache~~ improved by v1.2.3)
6683
- [x] hot reload web static server(use SO_REUSEPORT *nix api, so it may be wrong with Windows).
6784
- [ ] ~~different config(cors/cache strategy/https and so on) for different domain.~~ (if this is needed?)
6885

6986
### version 1.1.x
87+
7088
- [x] more doc(how to update static files)
7189
- [x] rewrite Dockerfile to reduce docker image size
7290
- [x] cache improve(big file ignore config option and if-range header support)
7391
- [x] header`cache-control` for client cache
7492
- [ ] ~~header `etag` for client cache~~ [warp #462](https://github.com/seanmonstar/warp/issues/462)
7593
- [x] 80 redirect to 443 config option
76-
- [x] compression for js/icon/json/css/html (only support gzip algo, only compress cached files, and ~~will occur error when client don't support gzip~~(fix @ v1.2))
94+
- [x] compression for js/icon/json/css/html (only support gzip algo, only compress cached files, and ~~will occur error
95+
when client don't support gzip~~(fix @ v1.2))
7796

7897
### version 1.0.x
98+
7999
- [x] 80 and 443 both support
80100
- [ ] ~~compression~~ done @ v1.2.0.
81101
- [ ] ~~multiple tls support~~ the feature may do not need.
82102
- [x] cache file(cache all files in memory without LRU)
83103

84104
### before release
105+
85106
- [x] very simple http1 spa server
86107
- [x] very simple admin server(http api)
87108
- [x] ssl(including wildcard domain ssl)

docs/index.md

+13-8
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,29 @@ sidebarDepth:2
33
---
44

55
# What is spa-server?
6+
67
spa-server is to provide a static web http server with cache and hot reload.
7-
It supports multiple config for different domain, and has a client tool(npm package, command line) to help upload static web files to server.
8+
It supports multiple config for different domain, and has a client tool(npm package, command line) to help upload static
9+
web files to server.
810

911
::: info Need Feedback
10-
sap-server features have been done, we are willing to get your feedback, fell free to open [issues](https://github.com/fornetcode/spa-server/issues).
12+
sap-server features have been done, we are willing to get your feedback, fell free to
13+
open [issues](https://github.com/fornetcode/spa-server/issues).
1114
:::
1215

13-
1416
## Motivation
15-
In my company, every single page application needs a nginx docker image, as time long, there containers takes lots of resources
16-
of memory and storage, and these nginx don't have proper config.
1717

18-
So I tried to develop a static web server to solve above problem, and create a client tool `spa-client` to help users to release
18+
In my company, every single page application needs a nginx docker image, as time long, there containers takes lots of
19+
resources
20+
of memory and storage, and these nginx don't have proper config.
21+
22+
So I tried to develop a static web server to solve above problem, and create a client tool `spa-client` to help users to
23+
release
1924
SPA.
2025

2126
## Feature
2227

23-
- Built with Hyper and Warp, fast and small!
28+
- Built with Hyper and Warp, fast and small!
2429
- SSL with Rustls.
2530
- Memory cache, client cache and compression(gzip).
2631
- Static web version control, you can regress or release new version easily.
@@ -29,4 +34,4 @@ SPA.
2934
- Http auto redirect to https.
3035
- Docker support(compressed size: 32M).
3136
- Provide command line/npm package to deploy spa.
32-
- Multiple configs for different domain.
37+
- Multiple configs for different domain and Multiple SPA in on domain.

server/src/domain_storage.rs

+36-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::file_cache::{CacheItem, FileCache};
2-
use anyhow::{anyhow, Context};
2+
use anyhow::{anyhow, bail, Context};
33
use dashmap::DashMap;
44
use lazy_static::lazy_static;
55
use md5::{Digest, Md5};
@@ -80,11 +80,13 @@ impl DomainStorage {
8080
let data = cache.cache_dir(
8181
domain_dir_name,
8282
Some(sub_path.as_str()),
83+
max_version,
8384
&path_buf,
8485
)?;
8586
cache.update(
8687
domain_dir_name.to_string(),
8788
Some(sub_path.as_str()),
89+
max_version,
8890
data,
8991
);
9092
match domain_version.get_mut(domain_dir_name) {
@@ -119,8 +121,9 @@ impl DomainStorage {
119121
let path_buf = path_prefix_buf
120122
.join(domain_dir_name)
121123
.join(max_version.to_string());
122-
let data = cache.cache_dir(domain_dir_name, None, &path_buf)?;
123-
cache.update(domain_dir_name.to_string(), None, data);
124+
let data =
125+
cache.cache_dir(domain_dir_name, None, max_version, &path_buf)?;
126+
cache.update(domain_dir_name.to_string(), None, max_version, data);
124127
domain_version.insert(
125128
domain_dir_name.to_string(),
126129
DomainMeta::OneWeb(path_buf, max_version),
@@ -288,8 +291,8 @@ impl DomainStorage {
288291
}
289292
};
290293
let path = if path == "" { None } else { Some(path) };
291-
let data = self.cache.cache_dir(host, path, &new_path)?;
292-
self.cache.update(host.to_string(), path, data);
294+
let data = self.cache.cache_dir(host, path, version, &new_path)?;
295+
self.cache.update(host.to_string(), path, version, data);
293296
debug!(
294297
"domain: {host} all keys:{:?}",
295298
self.cache.get_all_keys(host)
@@ -568,6 +571,27 @@ impl DomainStorage {
568571
));
569572
}
570573
let mut p = self.get_version_path(&domain, version);
574+
let (host, path) = match domain.split_once('/') {
575+
Some(v) => v,
576+
None => (domain.as_str(), ""),
577+
};
578+
let multiple = self.prefix.join(host).join(MULTIPLE_WEB_FILE_NAME);
579+
if path != "" {
580+
if !multiple.exists() {
581+
let parent = self.prefix.join(host);
582+
if !parent.exists() && fs::create_dir_all(&parent).is_err() {
583+
bail!(
584+
"create host directory {} failure",
585+
parent.display().to_string()
586+
)
587+
}
588+
if File::create_new(multiple).is_err() {
589+
bail!("files in same domain should not create at same time")
590+
}
591+
}
592+
} else if multiple.exists() {
593+
bail!("This already has multiple SPA, should not upload single SPA at top path")
594+
}
571595
fs::create_dir_all(&p)?;
572596
p.push(UPLOADING_FILE_NAME);
573597
File::create(p)?;
@@ -676,6 +700,7 @@ mod test {
676700
use crate::domain_storage::URI_REGEX_STR;
677701
use hyper::Uri;
678702
use regex::Regex;
703+
use std::fs::File;
679704
use std::path::PathBuf;
680705
use std::str::FromStr;
681706

@@ -731,4 +756,10 @@ mod test {
731756
// assert!(path.join("usr/lib/pam/").is_dir());
732757
// println!("{:?}", path.join("usr/lib/pam/").to_str());
733758
// }
759+
760+
// would crea
761+
// #[test]
762+
// fn test_create_file() {
763+
// File::create_new(PathBuf::from("/tmp/bccskwef")).unwrap();
764+
// }
734765
}

server/src/file_cache.rs

+48-18
Original file line numberDiff line numberDiff line change
@@ -104,39 +104,50 @@ impl FileCache {
104104
&self,
105105
domain: String,
106106
sub_path: Option<&str>,
107+
version: u32,
107108
data: HashMap<String, Arc<CacheItem>>,
108109
) {
109-
let data = match sub_path {
110-
Some(sub_path) => match self.data.get(&domain) {
111-
Some(ref info) => {
112-
let mut new_hash_map: HashMap<String, Arc<CacheItem>> = info
113-
.value()
114-
.iter()
115-
.filter_map(|(v, k)| {
116-
if v.starts_with(sub_path) {
110+
let data = match self.data.get(&domain) {
111+
Some(ref info) => {
112+
let mut new_hash_map: HashMap<String, Arc<CacheItem>> = info
113+
.value()
114+
.iter()
115+
.filter_map(|(v, k)| match sub_path {
116+
Some(sub_path) => {
117+
if v.starts_with(sub_path)
118+
&& version > k.version
119+
&& version - k.version > 2
120+
{
117121
None
118122
} else {
119123
Some((v.clone(), k.clone()))
120124
}
121-
})
122-
.collect();
123-
// inesrt before get would trigger deadlock. so move out insert function
124-
//drop(info);
125-
new_hash_map.extend(data);
126-
new_hash_map
127-
//self.data.insert(domain, new_hash_map);
128-
}
129-
None => data,
130-
},
125+
}
126+
None => {
127+
if version > k.version && version - k.version > 2 {
128+
None
129+
} else {
130+
Some((v.clone(), k.clone()))
131+
}
132+
}
133+
})
134+
.collect();
135+
// inesrt before get would trigger deadlock. so move out insert function
136+
//drop(info);
137+
new_hash_map.extend(data);
138+
new_hash_map
139+
}
131140
None => data,
132141
};
142+
133143
self.data.insert(domain, data);
134144
}
135145

136146
pub fn cache_dir(
137147
&self,
138148
domain: &str, //www.example.com
139149
sub_path: Option<&str>,
150+
version: u32,
140151
path: &PathBuf,
141152
) -> anyhow::Result<HashMap<String, Arc<CacheItem>>> {
142153
let prefix = path
@@ -147,6 +158,7 @@ impl FileCache {
147158
let conf = self.conf.get_domain_cache_config(domain);
148159
let expire_config = self.conf.get_domain_expire_config(domain);
149160
let result: HashMap<String, Arc<CacheItem>> = WalkDir::new(path)
161+
.min_depth(1)
150162
.into_iter()
151163
.filter_map(|x| x.ok())
152164
.filter_map(|entry| {
@@ -208,6 +220,7 @@ impl FileCache {
208220
mime,
209221
meta: metadata,
210222
data: data_block,
223+
version,
211224
expire: expire_config.get(&extension_name).cloned(),
212225
}),
213226
)
@@ -239,6 +252,7 @@ impl FileCache {
239252
}
240253
}
241254

255+
#[derive(Debug)]
242256
pub enum DataBlock {
243257
CacheBlock {
244258
bytes: Bytes,
@@ -263,6 +277,7 @@ pub struct CacheItem {
263277
pub meta: Metadata,
264278
pub data: DataBlock,
265279
pub mime: Mime,
280+
pub version: u32,
266281
pub expire: Option<Duration>,
267282
// pub etag: ETag,
268283
}
@@ -278,3 +293,18 @@ pub fn etag_calculate(meta: &Metadata, real_len: u64) -> anyhow::Result<ETag> {
278293
};
279294
Ok(etag)
280295
}*/
296+
297+
#[cfg(test)]
298+
mod test {
299+
use std::collections::HashMap;
300+
301+
#[test]
302+
fn test_extend() {
303+
let mut hash = HashMap::new();
304+
hash.insert(1, 1);
305+
let mut hash2 = HashMap::new();
306+
hash2.insert(1, 2);
307+
hash.extend(hash2);
308+
println!("hash2 {:?}", hash.get(&1));
309+
}
310+
}
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1 index
1+
1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1
1+
1 index
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2
1+
2 index
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3
1+
3 index
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3
1+
4-3
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
4
1+
4 index

0 commit comments

Comments
 (0)