20
20
*/
21
21
package eu .openanalytics .containerproxy .util ;
22
22
23
+ import eu .openanalytics .containerproxy .model .runtime .Proxy ;
24
+ import eu .openanalytics .containerproxy .model .runtime .ProxyStatus ;
25
+ import eu .openanalytics .containerproxy .service .AsyncProxyService ;
26
+ import eu .openanalytics .containerproxy .service .ProxyService ;
27
+ import eu .openanalytics .containerproxy .service .StructuredLogger ;
23
28
import eu .openanalytics .containerproxy .service .hearbeat .HeartbeatService ;
29
+ import io .undertow .io .Sender ;
30
+ import io .undertow .server .DefaultResponseListener ;
24
31
import io .undertow .server .HttpHandler ;
25
32
import io .undertow .server .HttpServerExchange ;
26
33
import io .undertow .server .handlers .PathHandler ;
31
38
import io .undertow .server .handlers .proxy .ProxyHandler ;
32
39
import io .undertow .servlet .handlers .ServletRequestContext ;
33
40
import io .undertow .util .AttachmentKey ;
41
+ import io .undertow .util .Headers ;
34
42
import io .undertow .util .PathMatcher ;
35
43
import org .springframework .context .annotation .Lazy ;
44
+ import io .undertow .util .StatusCodes ;
36
45
import org .springframework .stereotype .Component ;
37
46
38
47
import javax .inject .Inject ;
@@ -58,15 +67,26 @@ public class ProxyMappingManager {
58
67
59
68
private static final String PROXY_INTERNAL_ENDPOINT = "/proxy_endpoint" ;
60
69
private static final AttachmentKey <ProxyMappingManager > ATTACHMENT_KEY_DISPATCHER = AttachmentKey .create (ProxyMappingManager .class );
61
-
70
+ private static final AttachmentKey <ProxyIdAttachment > ATTACHMENT_KEY_PROXY_ID = AttachmentKey .create (ProxyIdAttachment .class );
71
+
72
+ private final StructuredLogger logger = StructuredLogger .create (getClass ());
73
+
62
74
private PathHandler pathHandler ;
63
75
64
76
private final Map <String , String > mappings = new HashMap <>();
65
77
66
78
@ Inject
67
79
@ Lazy
68
80
private HeartbeatService heartbeatService ;
69
-
81
+
82
+ @ Inject
83
+ @ Lazy
84
+ private ProxyService proxyService ;
85
+
86
+ @ Inject
87
+ @ Lazy
88
+ private AsyncProxyService asyncProxyService ;
89
+
70
90
public synchronized HttpHandler createHttpHandler (HttpHandler defaultHandler ) {
71
91
if (pathHandler == null ) {
72
92
pathHandler = new ProxyPathHandler (defaultHandler );
@@ -123,32 +143,70 @@ public String getProxyId(String mapping) {
123
143
*
124
144
* Note that clients can never access a proxy handler directly (for security reasons).
125
145
* Dispatching is the only allowed method to access proxy handlers.
126
- *
146
+ *
147
+ * @param proxyId The id of the proxy
127
148
* @param mapping The target mapping to dispatch to.
128
149
* @param request The request to dispatch.
129
150
* @param response The response corresponding to the request.
130
151
* @throws IOException If the dispatch fails for an I/O reason.
131
152
* @throws ServletException If the dispatch fails for any other reason.
132
153
*/
133
- public void dispatchAsync (String mapping , HttpServletRequest request , HttpServletResponse response ) throws IOException , ServletException {
134
- dispatchAsync (mapping , request , response , null );
154
+ public void dispatchAsync (String proxyId , String mapping , HttpServletRequest request , HttpServletResponse response ) throws IOException , ServletException {
155
+ dispatchAsync (proxyId , mapping , request , response , null );
135
156
}
136
157
137
- public void dispatchAsync (String mapping , HttpServletRequest request , HttpServletResponse response , Consumer <HttpServerExchange > exchangeCustomizer ) throws IOException , ServletException {
158
+ public void dispatchAsync (String proxyId , String mapping , HttpServletRequest request , HttpServletResponse response , Consumer <HttpServerExchange > exchangeCustomizer ) throws IOException , ServletException {
138
159
HttpServerExchange exchange = ServletRequestContext .current ().getExchange ();
139
160
exchange .putAttachment (ATTACHMENT_KEY_DISPATCHER , this );
140
-
161
+ exchange .putAttachment (ATTACHMENT_KEY_PROXY_ID , new ProxyIdAttachment (proxyId ));
162
+
141
163
String queryString = request .getQueryString ();
142
164
queryString = (queryString == null ) ? "" : "?" + queryString ;
143
165
String targetPath = PROXY_INTERNAL_ENDPOINT + "/" + mapping + queryString ;
144
166
145
167
if (exchangeCustomizer != null ) {
146
168
exchangeCustomizer .accept (exchange );
147
169
}
170
+ exchange .addDefaultResponseListener (defaultResponseListener );
148
171
request .startAsync ();
149
172
request .getRequestDispatcher (targetPath ).forward (request , response );
150
173
}
151
174
175
+ private final DefaultResponseListener defaultResponseListener = responseExchange -> {
176
+ if (!responseExchange .isResponseChannelAvailable ()) {
177
+ return false ;
178
+ }
179
+ if (responseExchange .getStatusCode () == StatusCodes .SERVICE_UNAVAILABLE ) {
180
+ ProxyIdAttachment proxyIdAttachment = responseExchange .getAttachment (ATTACHMENT_KEY_PROXY_ID );
181
+ Proxy proxy = null ;
182
+ if (proxyIdAttachment != null ) {
183
+ try {
184
+ proxy = proxyService .getProxy (proxyIdAttachment .proxyId );
185
+ if (proxy != null ) {
186
+ logger .info (proxy , "Proxy unreachable/crashed, stopping it now" );
187
+ asyncProxyService .stopProxy (proxy , true );
188
+ }
189
+ } catch (Throwable t ) {
190
+ // ignore in order to complete request
191
+ }
192
+ }
193
+
194
+ String errorPage ;
195
+ if (proxy != null && proxy .getStatus () != ProxyStatus .Stopped ) {
196
+ errorPage = "{\" status\" :\" error\" , \" message\" :\" app_crashed\" }" ;
197
+ } else {
198
+ // in-progress request got terminated because the app has been stopped (not crashed)
199
+ errorPage = "{\" status\" :\" error\" , \" message\" :\" app_stopped_or_non_existent\" }" ;
200
+ }
201
+ responseExchange .getResponseHeaders ().put (Headers .CONTENT_LENGTH , "" + errorPage .length ());
202
+ responseExchange .getResponseHeaders ().put (Headers .CONTENT_TYPE , "application/json" );
203
+ Sender sender = responseExchange .getResponseSender ();
204
+ sender .send (errorPage );
205
+ return true ;
206
+ }
207
+ return false ;
208
+ };
209
+
152
210
private static class ProxyPathHandler extends PathHandler {
153
211
154
212
public ProxyPathHandler (HttpHandler defaultHandler ) {
@@ -173,4 +231,13 @@ public void handleRequest(HttpServerExchange exchange) throws Exception {
173
231
}
174
232
}
175
233
}
234
+
235
+ private static class ProxyIdAttachment {
236
+ final String proxyId ;
237
+
238
+ public ProxyIdAttachment (String proxyId ) {
239
+ this .proxyId = proxyId ;
240
+ }
241
+ }
242
+
176
243
}
0 commit comments