2
2
//! with "/api" and the Accept header contains "html".
3
3
4
4
use super :: prelude:: * ;
5
+ use std:: {
6
+ collections:: { hash_map:: Entry , HashMap } ,
7
+ fmt:: Write ,
8
+ io:: Cursor ,
9
+ } ;
5
10
6
- use crate :: util:: RequestProxy ;
11
+ use crate :: util:: { errors:: NotFound , RequestProxy } ;
12
+
13
+ use reqwest:: blocking:: Client ;
7
14
8
15
// Can't derive debug because of Handler and Static.
9
16
#[ allow( missing_debug_implementations) ]
10
17
pub struct EmberIndexRewrite {
11
18
handler : Option < Box < dyn Handler > > ,
19
+ fastboot_client : Option < Client > ,
12
20
}
13
21
14
22
impl Default for EmberIndexRewrite {
15
23
fn default ( ) -> EmberIndexRewrite {
16
- EmberIndexRewrite { handler : None }
24
+ let fastboot_client = match dotenv:: var ( "USE_FASTBOOT" ) {
25
+ Ok ( val) if val == "staging-experimental" => Some ( Client :: new ( ) ) ,
26
+ _ => None ,
27
+ } ;
28
+
29
+ EmberIndexRewrite {
30
+ handler : None ,
31
+ fastboot_client,
32
+ }
17
33
}
18
34
}
19
35
@@ -25,21 +41,78 @@ impl AroundMiddleware for EmberIndexRewrite {
25
41
26
42
impl Handler for EmberIndexRewrite {
27
43
fn call ( & self , req : & mut dyn Request ) -> Result < Response > {
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
- . find ( "Accept" )
33
- . map ( |accept| accept. iter ( ) . any ( |s| s. contains ( "html" ) ) )
34
- . unwrap_or ( false ) ;
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" ) ;
38
44
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 {
45
+
46
+ if req. path ( ) . starts_with ( "/api" ) {
42
47
handler. call ( req)
48
+ } else {
49
+ if let Some ( client) = & self . fastboot_client {
50
+ // During local fastboot development, forward requests to the local fastboot server.
51
+ // In prodution, including when running with fastboot, nginx proxies the requests
52
+ // to the correct endpoint and requests should never make it here.
53
+ return proxy_to_fastboot ( client, req) . map_err ( |e| Box :: new ( e) as BoxError ) ;
54
+ }
55
+
56
+ if req
57
+ . headers ( )
58
+ . find ( "Accept" )
59
+ . map ( |accept| accept. iter ( ) . any ( |s| s. contains ( "html" ) ) )
60
+ . unwrap_or ( false )
61
+ {
62
+ // Serve static Ember page to bootstrap the frontend
63
+ handler. call ( & mut RequestProxy :: rewrite_path ( req, "/index.html" ) )
64
+ } else {
65
+ // Return a 404 to crawlers that don't send `Accept: text/hml`.
66
+ // This is to preserve legacy behavior and will likely change.
67
+ // Most of these crawlers probably won't execute our frontend JS anyway, but
68
+ // it would be nice to bootstrap the app for crawlers that do execute JS.
69
+ Ok ( NotFound . into ( ) )
70
+ }
43
71
}
44
72
}
45
73
}
74
+
75
+ /// Proxy to the fastboot server in development mode
76
+ ///
77
+ /// This handler is somewhat hacky, and is not intended for usage in production.
78
+ ///
79
+ /// # Panics
80
+ ///
81
+ /// This function can panic and should only be used in development mode.
82
+ fn proxy_to_fastboot ( client : & Client , req : & mut dyn Request ) -> ConcreteResult < Response > {
83
+ if req. method ( ) != conduit:: Method :: Get {
84
+ return Err ( format ! ( "Only support GET but request method was {}" , req. method( ) ) . into ( ) ) ;
85
+ }
86
+
87
+ let mut url = format ! ( "http://127.0.0.1:9000{}" , req. path( ) ) ;
88
+ if let Some ( query) = req. query_string ( ) {
89
+ write ! ( url, "?{}" , query) . map_err ( |e| e. to_string ( ) ) ?;
90
+ }
91
+ let mut request = client. request ( reqwest:: Method :: GET , & * url) ;
92
+ for header in req. headers ( ) . all ( ) {
93
+ for value in header. 1 {
94
+ request = request. header ( header. 0 , value) ;
95
+ }
96
+ }
97
+
98
+ let mut fastboot_response = request. send ( ) ?;
99
+ let mut headers: HashMap < String , Vec < String > > = HashMap :: new ( ) ;
100
+ for header in fastboot_response. headers ( ) {
101
+ match headers. entry ( header. 0 . to_string ( ) ) {
102
+ Entry :: Occupied ( mut v) => {
103
+ v. get_mut ( ) . push ( header. 1 . to_str ( ) . unwrap ( ) . to_owned ( ) ) ;
104
+ }
105
+ Entry :: Vacant ( v) => {
106
+ v. insert ( vec ! [ header. 1 . to_str( ) . unwrap( ) . to_owned( ) ] ) ;
107
+ }
108
+ } ;
109
+ }
110
+
111
+ let mut body = Vec :: new ( ) ;
112
+ fastboot_response. copy_to ( & mut body) ?;
113
+ Ok ( conduit:: Response {
114
+ status : ( fastboot_response. status ( ) . as_u16 ( ) as u32 , "OK" ) ,
115
+ headers,
116
+ body : Box :: new ( Cursor :: new ( body) ) ,
117
+ } )
118
+ }
0 commit comments