2020 */
2121package eu .openanalytics .containerproxy .util ;
2222
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 ;
2328import eu .openanalytics .containerproxy .service .hearbeat .HeartbeatService ;
29+ import io .undertow .io .Sender ;
30+ import io .undertow .server .DefaultResponseListener ;
2431import io .undertow .server .HttpHandler ;
2532import io .undertow .server .HttpServerExchange ;
2633import io .undertow .server .handlers .PathHandler ;
3138import io .undertow .server .handlers .proxy .ProxyHandler ;
3239import io .undertow .servlet .handlers .ServletRequestContext ;
3340import io .undertow .util .AttachmentKey ;
41+ import io .undertow .util .Headers ;
3442import io .undertow .util .PathMatcher ;
3543import org .springframework .context .annotation .Lazy ;
44+ import io .undertow .util .StatusCodes ;
3645import org .springframework .stereotype .Component ;
3746
3847import javax .inject .Inject ;
@@ -58,15 +67,26 @@ public class ProxyMappingManager {
5867
5968 private static final String PROXY_INTERNAL_ENDPOINT = "/proxy_endpoint" ;
6069 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+
6274 private PathHandler pathHandler ;
6375
6476 private final Map <String , String > mappings = new HashMap <>();
6577
6678 @ Inject
6779 @ Lazy
6880 private HeartbeatService heartbeatService ;
69-
81+
82+ @ Inject
83+ @ Lazy
84+ private ProxyService proxyService ;
85+
86+ @ Inject
87+ @ Lazy
88+ private AsyncProxyService asyncProxyService ;
89+
7090 public synchronized HttpHandler createHttpHandler (HttpHandler defaultHandler ) {
7191 if (pathHandler == null ) {
7292 pathHandler = new ProxyPathHandler (defaultHandler );
@@ -123,32 +143,70 @@ public String getProxyId(String mapping) {
123143 *
124144 * Note that clients can never access a proxy handler directly (for security reasons).
125145 * Dispatching is the only allowed method to access proxy handlers.
126- *
146+ *
147+ * @param proxyId The id of the proxy
127148 * @param mapping The target mapping to dispatch to.
128149 * @param request The request to dispatch.
129150 * @param response The response corresponding to the request.
130151 * @throws IOException If the dispatch fails for an I/O reason.
131152 * @throws ServletException If the dispatch fails for any other reason.
132153 */
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 );
135156 }
136157
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 {
138159 HttpServerExchange exchange = ServletRequestContext .current ().getExchange ();
139160 exchange .putAttachment (ATTACHMENT_KEY_DISPATCHER , this );
140-
161+ exchange .putAttachment (ATTACHMENT_KEY_PROXY_ID , new ProxyIdAttachment (proxyId ));
162+
141163 String queryString = request .getQueryString ();
142164 queryString = (queryString == null ) ? "" : "?" + queryString ;
143165 String targetPath = PROXY_INTERNAL_ENDPOINT + "/" + mapping + queryString ;
144166
145167 if (exchangeCustomizer != null ) {
146168 exchangeCustomizer .accept (exchange );
147169 }
170+ exchange .addDefaultResponseListener (defaultResponseListener );
148171 request .startAsync ();
149172 request .getRequestDispatcher (targetPath ).forward (request , response );
150173 }
151174
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+
152210 private static class ProxyPathHandler extends PathHandler {
153211
154212 public ProxyPathHandler (HttpHandler defaultHandler ) {
@@ -173,4 +231,13 @@ public void handleRequest(HttpServerExchange exchange) throws Exception {
173231 }
174232 }
175233 }
234+
235+ private static class ProxyIdAttachment {
236+ final String proxyId ;
237+
238+ public ProxyIdAttachment (String proxyId ) {
239+ this .proxyId = proxyId ;
240+ }
241+ }
242+
176243}
0 commit comments