1+ use crate :: errors:: PrerenderError ;
12use crate :: { IGNORED_EXTENSIONS , USER_AGENTS } ;
2- use actix_web:: dev:: ServiceRequest ;
3+ use actix_service:: { Service , Transform } ;
4+ use actix_utils:: future;
5+ use actix_utils:: future:: Ready ;
6+ use actix_web:: body:: BoxBody ;
7+
8+ use actix_web:: dev:: { ServiceRequest , ServiceResponse } ;
39use actix_web:: http:: header:: HeaderMap ;
410use actix_web:: http:: uri:: PathAndQuery ;
511use actix_web:: http:: { header, Method } ;
12+ use actix_web:: { Error , HttpResponse } ;
13+ use futures_util:: future:: LocalBoxFuture ;
14+ use futures_util:: TryFutureExt ;
15+ use reqwest:: Client ;
16+ use url:: Url ;
617
7- #[ derive( Debug ) ]
8- struct PrerenderMiddleware {
9- prerender_service_url : String ,
18+ #[ derive( Debug , Clone ) ]
19+ pub struct Prerender {
20+ prerender_service_url : Url ,
21+ inner_client : Client ,
1022}
1123
12- impl PrerenderMiddleware {
13- pub fn prepare_build_api_url ( & self , req : & ServiceRequest ) -> String {
14- let req_uri = req. uri ( ) ;
15- let req_headers = req. headers ( ) ;
16-
17- // TODO: this.host?
18- let host = req
19- . uri ( )
20- . host ( )
21- . or_else ( || {
22- req_headers
23- . get ( "X-Forwarded-Host" )
24- . and_then ( |hdr| hdr. to_str ( ) . ok ( ) )
25- } )
26- . or_else ( || {
27- req_headers
28- . get ( header:: HOST )
29- . and_then ( |hdr| hdr. to_str ( ) . ok ( ) )
30- } )
31- . unwrap ( ) ;
32-
33- let scheme = req. uri ( ) . scheme_str ( ) . unwrap_or ( "http" ) ;
34- let url_path_query = req_uri. path_and_query ( ) . map ( PathAndQuery :: as_str) . unwrap ( ) ;
35-
36- format ! (
37- "{}{}://{}{}" ,
38- & * self . prerender_service_url, scheme, host, url_path_query
39- )
40- }
24+ #[ derive( Debug , Clone ) ]
25+ pub struct Inner {
26+ prerender_service_url : Url ,
27+ inner_client : Client ,
4128}
4229
30+ impl Prerender { }
31+
4332#[ derive( Debug ) ]
44- struct PrerenderMiddlewareBuilder { }
33+ pub struct PrerenderBuilder { }
4534
46- impl PrerenderMiddlewareBuilder {
47- pub fn use_prerender_io ( ) -> PrerenderMiddleware {
48- PrerenderMiddleware {
49- prerender_service_url : prerender_url ( ) . to_string ( ) ,
35+ impl PrerenderBuilder {
36+ pub fn use_prerender_io ( mut self ) -> Prerender {
37+ Prerender {
38+ prerender_service_url : prerender_url ( ) ,
39+ inner_client : Default :: default ( ) ,
5040 }
5141 }
5242
53- pub fn use_custom_prerender_url ( prerender_service_url : & impl ToString ) -> PrerenderMiddleware {
54- PrerenderMiddleware {
55- prerender_service_url : prerender_service_url. to_string ( ) ,
56- }
43+ pub fn use_custom_prerender_url (
44+ mut self ,
45+ prerender_service_url : & str ,
46+ ) -> Result < Prerender , PrerenderError > {
47+ let result = Url :: parse ( prerender_service_url) . map_err ( |_| PrerenderError :: InvalidUrl ) ?;
48+
49+ Ok ( Prerender {
50+ prerender_service_url : result,
51+ inner_client : Default :: default ( ) ,
52+ } )
5753 }
5854}
5955
60- impl PrerenderMiddleware {
61- pub fn builder ( ) -> PrerenderMiddlewareBuilder {
62- PrerenderMiddlewareBuilder { }
56+ impl Prerender {
57+ pub fn builder ( ) -> PrerenderBuilder {
58+ PrerenderBuilder { }
6359 }
6460}
6561
62+ fn prerender_url ( ) -> Url {
63+ Url :: parse ( "https://service.prerender.io" ) . unwrap ( )
64+ }
65+
6666/// Decides if should prerender the page or not.
6767///
6868/// Will NOT prerender on the following cases:
6969/// * HTTP is not GET or HEAD
7070/// * User agent is NOT crawler
7171/// * Is requesting a resource on `IGNORED_EXTENSIONS`
72- pub fn should_prerender ( req : & ServiceRequest ) -> bool {
72+ pub ( crate ) fn should_prerender ( req : & ServiceRequest ) -> bool {
7373 let request_headers = req. headers ( ) ;
7474 let mut is_crawler = false ;
7575
@@ -78,9 +78,9 @@ pub fn should_prerender(req: &ServiceRequest) -> bool {
7878 }
7979
8080 let req_ua_lowercase = if let Some ( user_agent) = request_headers. get ( header:: USER_AGENT ) {
81- let user_agent = user_agent. to_str ( ) ;
81+ let user_agent = user_agent. to_str ( ) . map ( str :: to_lowercase ) ;
8282 if let Ok ( ua) = user_agent {
83- ua. to_lowercase ( )
83+ ua
8484 } else {
8585 return false ;
8686 }
@@ -111,37 +111,135 @@ pub fn should_prerender(req: &ServiceRequest) -> bool {
111111 is_crawler
112112}
113113
114- pub fn get_prerendered_response ( req : ServiceRequest ) {
115- let mut prerender_request_headers = HeaderMap :: new ( ) ;
116- let forward_headers = true ;
114+ #[ derive( Debug ) ]
115+ pub struct PrerenderMiddleware < S > {
116+ pub ( crate ) service : S ,
117+ prerender_service_url : Url ,
118+ inner_client : Client ,
119+ }
117120
118- if forward_headers {
119- prerender_request_headers = req. headers ( ) . clone ( ) ;
120- prerender_request_headers. remove ( header:: HOST ) ;
121+ impl < S > PrerenderMiddleware < S > {
122+ pub fn prepare_build_api_url ( & self , req : & ServiceRequest ) -> String {
123+ let req_uri = req. uri ( ) ;
124+ let req_headers = req. headers ( ) ;
125+
126+ // TODO: this.host?
127+ let host = req
128+ . uri ( )
129+ . host ( )
130+ . or_else ( || {
131+ req_headers
132+ . get ( "X-Forwarded-Host" )
133+ . and_then ( |hdr| hdr. to_str ( ) . ok ( ) )
134+ } )
135+ . or_else ( || {
136+ req_headers
137+ . get ( header:: HOST )
138+ . and_then ( |hdr| hdr. to_str ( ) . ok ( ) )
139+ } )
140+ . unwrap ( ) ;
141+
142+ let scheme = req. uri ( ) . scheme_str ( ) . unwrap_or ( "http" ) ;
143+ let url_path_query = req_uri. path_and_query ( ) . map ( PathAndQuery :: as_str) . unwrap ( ) ;
144+
145+ format ! (
146+ "{}{}://{}{}" ,
147+ self . prerender_service_url, scheme, host, url_path_query
148+ )
149+ }
150+
151+ pub async fn get_rendered_response (
152+ & self ,
153+ req : ServiceRequest ,
154+ ) -> Result < ServiceResponse , PrerenderError > {
155+ let mut prerender_request_headers = HeaderMap :: new ( ) ;
156+ let forward_headers = true ;
157+
158+ if forward_headers {
159+ prerender_request_headers = req. headers ( ) . clone ( ) ;
160+ prerender_request_headers. remove ( header:: HOST ) ;
161+ }
162+
163+ prerender_request_headers. append ( header:: ACCEPT_ENCODING , "gzip" . parse ( ) . unwrap ( ) ) ;
164+
165+ // TODO: accept `X-Prerender-Token`
166+ // prerender_request_headers.insert("X-Prerender-Token", pre_render_token);
167+
168+ let url_to_request = self . prepare_build_api_url ( & req) ;
169+ let prerender_response = self
170+ . inner_client
171+ . get ( url_to_request)
172+ . send ( )
173+ . and_then ( |a| a. bytes ( ) )
174+ . await ?;
175+
176+ let http_response = HttpResponse :: Ok ( ) . body ( prerender_response) ;
177+ Ok ( req. into_response ( http_response) )
121178 }
179+ }
180+
181+ impl < S > Service < ServiceRequest > for PrerenderMiddleware < S >
182+ where
183+ S : Service < ServiceRequest , Response = ServiceResponse , Error = Error > ,
184+ S :: Future : ' static ,
185+ {
186+ type Response = ServiceResponse < BoxBody > ;
187+ type Error = Error ;
188+ type Future = LocalBoxFuture < ' static , Result < ServiceResponse < BoxBody > , Error > > ;
189+
190+ actix_service:: forward_ready!( service) ;
122191
123- prerender_request_headers. append ( header:: ACCEPT_ENCODING , "gzip" . parse ( ) . unwrap ( ) ) ;
192+ fn call ( & self , req : ServiceRequest ) -> Self :: Future {
193+ // life goes on
194+ if !should_prerender ( & req) {
195+ let fut = self . service . call ( req) ;
196+ return Box :: pin ( async move { fut. await } ) ;
197+ }
124198
125- // TODO: accept `X-Prerender-Token`
126- // prerender_request_headers.insert("X-Prerender-Token", pre_render_token);
199+ // let response = self.get_rendered_response(req).await.map_err(|e| );
200+ todo ! ( )
201+ }
127202}
128203
129- pub fn prerender_url ( ) -> & ' static str {
130- "https://service.prerender.io/"
204+ impl < S > Transform < S , ServiceRequest > for Prerender
205+ where
206+ S : Service < ServiceRequest , Response = ServiceResponse , Error = Error > ,
207+ S :: Future : ' static ,
208+ {
209+ type Response = ServiceResponse < BoxBody > ;
210+ type Error = Error ;
211+ type Transform = PrerenderMiddleware < S > ;
212+ type InitError = ( ) ;
213+ type Future = Ready < Result < Self :: Transform , Self :: InitError > > ;
214+
215+ fn new_transform ( & self , service : S ) -> Self :: Future {
216+ future:: ok ( PrerenderMiddleware {
217+ service,
218+ prerender_service_url : self . prerender_service_url . clone ( ) ,
219+ inner_client : self . inner_client . clone ( ) ,
220+ } )
221+ }
131222}
132223
133224#[ cfg( test) ]
134225mod tests {
135- use crate :: middleware:: should_prerender;
136- use actix_web:: http:: { header, Method } ;
226+ use crate :: middleware:: { prerender_url, should_prerender, Prerender , PrerenderMiddleware } ;
227+ use actix_service:: Transform ;
228+ use actix_web:: http:: header;
229+ use actix_web:: middleware:: Compat ;
137230 use actix_web:: test:: TestRequest ;
231+ use actix_web:: { test, App } ;
138232
139233 fn init_logger ( ) {
140234 let _ = env_logger:: builder ( ) . is_test ( true ) . try_init ( ) ;
141235 }
142236
143- #[ test]
144- fn test_human_valid_resource ( ) {
237+ fn compat_compat ( ) {
238+ let _ = App :: new ( ) . wrap ( Compat :: new ( Prerender :: builder ( ) . use_prerender_io ( ) ) ) ;
239+ }
240+
241+ #[ actix_web:: test]
242+ async fn test_human_valid_resource ( ) {
145243 let req = TestRequest :: get ( )
146244 . insert_header ( (
147245 header:: USER_AGENT ,
@@ -153,8 +251,8 @@ mod tests {
153251 assert ! ( !should_prerender( & req) ) ;
154252 }
155253
156- #[ test]
157- fn test_crawler_valid_resource ( ) {
254+ #[ actix_web :: test]
255+ async fn test_crawler_valid_resource ( ) {
158256 let req = TestRequest :: get ( )
159257 . insert_header ( (
160258 header:: USER_AGENT ,
@@ -166,8 +264,8 @@ mod tests {
166264 assert ! ( should_prerender( & req) ) ;
167265 }
168266
169- #[ test]
170- fn test_crawler_ignored_resource ( ) {
267+ #[ actix_web :: test]
268+ async fn test_crawler_ignored_resource ( ) {
171269 let req = TestRequest :: get ( )
172270 . insert_header ( (
173271 header:: USER_AGENT ,
@@ -180,8 +278,8 @@ mod tests {
180278 assert ! ( !render) ;
181279 }
182280
183- #[ test]
184- fn test_crawler_wrong_http_method ( ) {
281+ #[ actix_web :: test]
282+ async fn test_crawler_wrong_http_method ( ) {
185283 let req = TestRequest :: post ( )
186284 . insert_header ( (
187285 header:: USER_AGENT ,
@@ -193,4 +291,31 @@ mod tests {
193291 let render = should_prerender ( & req) ;
194292 assert ! ( !render) ;
195293 }
294+
295+ fn create_middleware ( ) -> Prerender {
296+ Prerender :: builder ( ) . use_prerender_io ( )
297+ }
298+
299+ #[ actix_web:: test]
300+ async fn test_redirect_url ( ) {
301+ let req_url = "http://yourserver.com/clothes/tshirts?query=xl" ;
302+
303+ let req = TestRequest :: get ( )
304+ . insert_header ( (
305+ header:: USER_AGENT ,
306+ "Mozilla/5.0 (X11; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0" ,
307+ ) )
308+ . uri ( req_url)
309+ . to_srv_request ( ) ;
310+
311+ let middleware = create_middleware ( )
312+ . new_transform ( test:: ok_service ( ) )
313+ . into_inner ( )
314+ . unwrap ( ) ;
315+
316+ assert_eq ! (
317+ middleware. prepare_build_api_url( & req) ,
318+ format!( "{}{}" , prerender_url( ) , req_url)
319+ ) ;
320+ }
196321}
0 commit comments