@@ -5,9 +5,16 @@ use tokio::sync::{Semaphore, Mutex};
5
5
use warp:: Filter ;
6
6
use serde:: Deserialize ;
7
7
use std:: collections:: VecDeque ;
8
+ use lru:: LruCache ;
9
+ use tokio:: time:: { Duration , Instant } ;
10
+ use std:: num:: NonZeroUsize ;
11
+ use std:: env;
12
+ use log:: { info, error} ;
8
13
9
14
#[ tokio:: main]
10
15
async fn main ( ) {
16
+ env_logger:: init ( ) ;
17
+
11
18
let args: Vec < & OsStr > = vec ! [
12
19
OsStr :: new( "--no-sandbox" ) ,
13
20
OsStr :: new( "--disable-gpu" ) ,
@@ -30,14 +37,23 @@ async fn main() {
30
37
let semaphore = Arc :: new ( Semaphore :: new ( 4 ) ) ;
31
38
let tab_pool = Arc :: new ( Mutex :: new ( VecDeque :: new ( ) ) ) ;
32
39
40
+ let cache_maxsize = env:: var ( "CACHE_MAXSIZE" )
41
+ . unwrap_or_else ( |_| "100" . to_string ( ) )
42
+ . parse :: < usize > ( )
43
+ . expect ( "CACHE_MAXSIZE must be a positive integer" ) ;
44
+
45
+ let cache_size = NonZeroUsize :: new ( cache_maxsize) . expect ( "CACHE_MAXSIZE must be a non-zero integer" ) ;
46
+ let cache = Arc :: new ( Mutex :: new ( LruCache :: new ( cache_size) ) ) ; // Cache max size from environment variable
47
+
33
48
let render_route = warp:: path ( "html" )
34
49
. and ( warp:: query :: < RenderQuery > ( ) )
35
50
. and ( with_browser ( browser. clone ( ) ) )
36
51
. and ( with_semaphore ( semaphore. clone ( ) ) )
37
52
. and ( with_tab_pool ( tab_pool. clone ( ) ) )
53
+ . and ( with_cache ( cache. clone ( ) ) )
38
54
. and_then ( render_handler) ;
39
55
40
- println ! ( "Server running on http://0.0.0.0:8080" ) ;
56
+ info ! ( "Server running on http://0.0.0.0:8080" ) ;
41
57
warp:: serve ( render_route) . run ( ( [ 0 , 0 , 0 , 0 ] , 8080 ) ) . await ;
42
58
}
43
59
@@ -64,6 +80,12 @@ fn with_tab_pool(
64
80
warp:: any ( ) . map ( move || tab_pool. clone ( ) )
65
81
}
66
82
83
+ fn with_cache (
84
+ cache : Arc < Mutex < LruCache < String , ( String , Instant ) > > > ,
85
+ ) -> impl Filter < Extract = ( Arc < Mutex < LruCache < String , ( String , Instant ) > > > , ) , Error = std:: convert:: Infallible > + Clone {
86
+ warp:: any ( ) . map ( move || cache. clone ( ) )
87
+ }
88
+
67
89
#[ derive( Debug ) ]
68
90
struct CustomError ;
69
91
@@ -74,32 +96,51 @@ async fn render_handler(
74
96
browser : Arc < Browser > ,
75
97
semaphore : Arc < Semaphore > ,
76
98
tab_pool : Arc < Mutex < VecDeque < Arc < Tab > > > > ,
99
+ cache : Arc < Mutex < LruCache < String , ( String , Instant ) > > > ,
77
100
) -> Result < impl warp:: Reply , warp:: Rejection > {
78
101
let _permit = semaphore. acquire ( ) . await ;
79
102
103
+ let cache_ttl_secs = env:: var ( "CACHE_TTL" )
104
+ . unwrap_or_else ( |_| "60" . to_string ( ) )
105
+ . parse :: < u64 > ( )
106
+ . expect ( "CACHE_TTL must be a positive integer" ) ;
107
+ let cache_ttl = Duration :: new ( cache_ttl_secs, 0 ) ;
108
+
109
+ info ! ( "Initial request to {}" , query. url) ;
110
+
111
+ let start_time = Instant :: now ( ) ;
112
+
113
+ let mut cache_guard = cache. lock ( ) . await ;
114
+ if let Some ( ( content, timestamp) ) = cache_guard. get ( & query. url ) {
115
+ if timestamp. elapsed ( ) < cache_ttl {
116
+ info ! ( "Cache hit for {}" , query. url) ;
117
+ return Ok ( warp:: reply:: html ( content. clone ( ) ) ) ;
118
+ }
119
+ }
120
+
80
121
let tab = {
81
122
let mut pool = tab_pool. lock ( ) . await ;
82
123
if let Some ( tab) = pool. pop_front ( ) {
83
124
tab
84
125
} else {
85
126
browser. new_tab ( ) . map_err ( |e| {
86
- eprintln ! ( "Failed to create new tab: {:?}" , e) ;
127
+ error ! ( "Failed to create new tab: {:?}" , e) ;
87
128
warp:: reject:: custom ( CustomError )
88
129
} ) ?
89
130
}
90
131
} ;
91
132
92
133
tab. navigate_to ( & query. url ) . map_err ( |e| {
93
- eprintln ! ( "Failed to navigate to URL: {:?}" , e) ;
134
+ error ! ( "Failed to navigate to URL: {:?}" , e) ;
94
135
warp:: reject:: custom ( CustomError )
95
136
} ) ?;
96
137
tab. wait_until_navigated ( ) . map_err ( |e| {
97
- eprintln ! ( "Failed to wait until navigated: {:?}" , e) ;
138
+ error ! ( "Failed to wait until navigated: {:?}" , e) ;
98
139
warp:: reject:: custom ( CustomError )
99
140
} ) ?;
100
141
101
142
let content = tab. get_content ( ) . map_err ( |e| {
102
- eprintln ! ( "Failed to get content: {:?}" , e) ;
143
+ error ! ( "Failed to get content: {:?}" , e) ;
103
144
warp:: reject:: custom ( CustomError )
104
145
} ) ?;
105
146
@@ -108,5 +149,10 @@ async fn render_handler(
108
149
pool. push_back ( Arc :: clone ( & tab) ) ;
109
150
}
110
151
152
+ cache_guard. put ( query. url . clone ( ) , ( content. clone ( ) , Instant :: now ( ) ) ) ;
153
+
154
+ let duration = start_time. elapsed ( ) ;
155
+ info ! ( "Got {} in {:?} for {}" , content. len( ) , duration, query. url) ;
156
+
111
157
Ok ( warp:: reply:: html ( content) )
112
158
}
0 commit comments