2
2
//! with "/api" and the Accept header contains "html".
3
3
4
4
use super :: prelude:: * ;
5
+ use std:: fmt:: Write ;
5
6
6
- use crate :: util:: RequestProxy ;
7
+ use crate :: util:: { errors:: NotFound , AppResponse , Error , RequestProxy } ;
8
+
9
+ use conduit:: { Body , HandlerResult } ;
10
+ use reqwest:: blocking:: Client ;
7
11
8
12
// Can't derive debug because of Handler and Static.
9
13
#[ allow( missing_debug_implementations) ]
10
14
pub struct EmberIndexRewrite {
11
15
handler : Option < Box < dyn Handler > > ,
16
+ fastboot_client : Option < Client > ,
12
17
}
13
18
14
19
impl Default for EmberIndexRewrite {
15
20
fn default ( ) -> EmberIndexRewrite {
16
- EmberIndexRewrite { handler : None }
21
+ let fastboot_client = match dotenv:: var ( "USE_FASTBOOT" ) {
22
+ Ok ( val) if val == "staging-experimental" => Some ( Client :: new ( ) ) ,
23
+ _ => None ,
24
+ } ;
25
+
26
+ EmberIndexRewrite {
27
+ handler : None ,
28
+ fastboot_client,
29
+ }
17
30
}
18
31
}
19
32
@@ -24,22 +37,65 @@ impl AroundMiddleware for EmberIndexRewrite {
24
37
}
25
38
26
39
impl Handler for EmberIndexRewrite {
27
- fn call ( & self , req : & mut dyn RequestExt ) -> AfterResult {
28
- // If the client is requesting html, then we've only got one page so
29
- // rewrite the request.
30
- let wants_html = req
31
- . headers ( )
32
- . get_all ( header:: ACCEPT )
33
- . iter ( )
34
- . any ( |val| val. to_str ( ) . unwrap_or_default ( ) . contains ( "html" ) ) ;
35
- // If the route starts with /api, just assume they want the API
36
- // response and fall through.
37
- let is_api_path = req. path ( ) . starts_with ( "/api" ) ;
40
+ fn call ( & self , req : & mut dyn RequestExt ) -> HandlerResult {
38
41
let handler = self . handler . as_ref ( ) . unwrap ( ) ;
39
- if wants_html && !is_api_path {
40
- handler. call ( & mut RequestProxy :: rewrite_path ( req, "/index.html" ) )
41
- } else {
42
+
43
+ if req. path ( ) . starts_with ( "/api" ) {
42
44
handler. call ( req)
45
+ } else {
46
+ if let Some ( client) = & self . fastboot_client {
47
+ // During local fastboot development, forward requests to the local fastboot server.
48
+ // In prodution, including when running with fastboot, nginx proxies the requests
49
+ // to the correct endpoint and requests should never make it here.
50
+ return proxy_to_fastboot ( client, req) . map_err ( box_error) ;
51
+ }
52
+
53
+ if req
54
+ . headers ( )
55
+ . get_all ( header:: ACCEPT )
56
+ . iter ( )
57
+ . any ( |val| val. to_str ( ) . unwrap_or_default ( ) . contains ( "html" ) )
58
+ {
59
+ // Serve static Ember page to bootstrap the frontend
60
+ handler. call ( & mut RequestProxy :: rewrite_path ( req, "/index.html" ) )
61
+ } else {
62
+ // Return a 404 to crawlers that don't send `Accept: text/hml`.
63
+ // This is to preserve legacy behavior and will likely change.
64
+ // Most of these crawlers probably won't execute our frontend JS anyway, but
65
+ // it would be nice to bootstrap the app for crawlers that do execute JS.
66
+ Ok ( NotFound . into ( ) )
67
+ }
43
68
}
44
69
}
45
70
}
71
+
72
+ /// Proxy to the fastboot server in development mode
73
+ ///
74
+ /// This handler is somewhat hacky, and is not intended for usage in production.
75
+ ///
76
+ /// # Panics
77
+ ///
78
+ /// This function can panic and should only be used in development mode.
79
+ fn proxy_to_fastboot ( client : & Client , req : & mut dyn RequestExt ) -> Result < AppResponse , Error > {
80
+ if req. method ( ) != conduit:: Method :: GET {
81
+ return Err ( format ! ( "Only support GET but request method was {}" , req. method( ) ) . into ( ) ) ;
82
+ }
83
+
84
+ let mut url = format ! ( "http://127.0.0.1:9000{}" , req. path( ) ) ;
85
+ if let Some ( query) = req. query_string ( ) {
86
+ write ! ( url, "?{}" , query) . map_err ( |e| e. to_string ( ) ) ?;
87
+ }
88
+ let mut fastboot_response = client
89
+ . request ( req. method ( ) . into ( ) , & * url)
90
+ . headers ( req. headers ( ) . clone ( ) )
91
+ . send ( ) ?;
92
+ let mut body = Vec :: new ( ) ;
93
+ fastboot_response. copy_to ( & mut body) ?;
94
+
95
+ let mut builder = Response :: builder ( ) . status ( fastboot_response. status ( ) ) ;
96
+ builder
97
+ . headers_mut ( )
98
+ . unwrap ( )
99
+ . extend ( fastboot_response. headers ( ) . clone ( ) ) ;
100
+ builder. body ( Body :: from_vec ( body) ) . map_err ( Into :: into)
101
+ }
0 commit comments