• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

aspectran / aspectran / #4052

10 Feb 2025 08:23AM CUT coverage: 35.294% (-0.003%) from 35.297%
#4052

push

github

topframe
Update

0 of 2 new or added lines in 1 file covered. (0.0%)

1 existing line in 1 file now uncovered.

14246 of 40364 relevant lines covered (35.29%)

0.35 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/web/src/main/java/com/aspectran/web/service/DefaultWebService.java
1
/*
2
 * Copyright (c) 2008-2025 The Aspectran Project
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
package com.aspectran.web.service;
17

18
import com.aspectran.core.activity.ActivityTerminatedException;
19
import com.aspectran.core.activity.TransletNotFoundException;
20
import com.aspectran.core.activity.request.RequestMethodNotAllowedException;
21
import com.aspectran.core.activity.request.SizeLimitExceededException;
22
import com.aspectran.core.component.session.MaxSessionsExceededException;
23
import com.aspectran.core.component.translet.TransletRuleRegistry;
24
import com.aspectran.core.context.ActivityContext;
25
import com.aspectran.core.context.rule.type.MethodType;
26
import com.aspectran.core.service.CoreService;
27
import com.aspectran.utils.ExceptionUtils;
28
import com.aspectran.utils.StringUtils;
29
import com.aspectran.utils.ToStringBuilder;
30
import com.aspectran.utils.annotation.jsr305.NonNull;
31
import com.aspectran.utils.annotation.jsr305.Nullable;
32
import com.aspectran.utils.logging.Logger;
33
import com.aspectran.utils.logging.LoggerFactory;
34
import com.aspectran.utils.thread.ThreadContextHelper;
35
import com.aspectran.web.activity.WebActivity;
36
import com.aspectran.web.support.http.HttpHeaders;
37
import com.aspectran.web.support.util.WebUtils;
38
import jakarta.servlet.AsyncContext;
39
import jakarta.servlet.AsyncEvent;
40
import jakarta.servlet.AsyncListener;
41
import jakarta.servlet.ServletContext;
42
import jakarta.servlet.http.HttpServletRequest;
43
import jakarta.servlet.http.HttpServletResponse;
44

45
import java.io.IOException;
46
import java.net.URLDecoder;
47

48
import static com.aspectran.core.component.session.MaxSessionsExceededException.MAX_SESSIONS_EXCEEDED;
49

50
/**
51
 * Provides overall functionality for building web applications within a web
52
 * application container.
53
 */
54
public class DefaultWebService extends AbstractWebService {
55

56
    private static final Logger logger = LoggerFactory.getLogger(DefaultWebService.class);
×
57

58
    protected volatile long pauseTimeout = -2L;
×
59

60
    DefaultWebService(@NonNull ServletContext servletContext, @Nullable CoreService parentService, boolean derived) {
61
        super(servletContext, parentService, derived);
×
62
    }
×
63

64
    @Override
65
    public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
66
        if (checkPaused(response)) {
×
67
            return;
×
68
        }
69

70
        final String requestUri;
71
        if (getUriDecoding() != null) {
×
72
            requestUri = URLDecoder.decode(request.getRequestURI(), getUriDecoding());
×
73
        } else {
74
            requestUri = request.getRequestURI();
×
75
        }
76

77
        final String requestName = WebUtils.getRelativePath(getContextPath(), requestUri);
×
78
        final MethodType requestMethod = MethodType.resolve(request.getMethod(), MethodType.GET);
×
79
        final String reverseContextPath = WebUtils.getReverseContextPath(request, getContextPath());
×
80

81
        if (logger.isDebugEnabled()) {
×
82
            logger.debug(getRequestInfo(request, reverseContextPath, requestName, requestMethod));
×
83
        }
84

85
        if (!isAcceptable(requestName)) {
×
86
            try {
87
                if (!getDefaultServletHttpRequestHandler().handleRequest(request, response)) {
×
88
                    sendError(response, HttpServletResponse.SC_NOT_FOUND, "Not Exposed");
×
89
                }
90
            } catch (Exception e) {
×
91
                logger.error("Error while processing with default servlet", e);
×
92
                sendError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, null);
×
93
            }
×
94
            return;
×
95
        }
96

97
        WebActivity activity = new WebActivity(this, getContextPath(), reverseContextPath, request, response);
×
98
        activity.setRequestName(requestName);
×
99
        activity.setRequestMethod(requestMethod);
×
100

101
        try {
102
            activity.prepare();
×
103
        } catch (TransletNotFoundException e) {
×
104
            transletNotFound(activity);
×
105
            return;
×
106
        } catch (Exception e) {
×
107
            sendError(activity, e);
×
108
            return;
×
109
        }
×
110

111
        WebService.bind(activity, this);
×
112

113
        if (activity.isAsync() && request.isAsyncSupported()) {
×
114
            asyncPerform(activity);
×
115
        } else {
116
            perform(activity);
×
117
        }
118
    }
×
119

120
    private void asyncPerform(@NonNull WebActivity activity) {
121
        final AsyncContext asyncContext;
122
        if (activity.getRequest().isAsyncStarted()) {
×
123
            asyncContext = activity.getRequest().getAsyncContext();
×
124
            if (activity.getTimeout() != null) {
×
125
                try {
126
                    asyncContext.setTimeout(activity.getTimeout());
×
127
                } catch (IllegalStateException ex) {
×
128
                    logger.warn("Servlet request has been put into asynchronous mode by an external force. " +
×
129
                            "Proceeding with the existing AsyncContext instance, " +
130
                            "but cannot guarantee the correct behavior of JAX-RS AsyncResponse time-out support.");
131
                }
×
132
            }
133
        } else {
134
            asyncContext = activity.getRequest().startAsync();
×
135
            if (logger.isDebugEnabled()) {
×
136
                logger.debug("Async Started " + asyncContext);
×
137
            }
138
            if (activity.getTimeout() != null) {
×
139
                asyncContext.setTimeout(activity.getTimeout());
×
140
            }
141
        }
142
        asyncContext.addListener(new AsyncListener() {
×
143
            @Override
144
            public void onComplete(AsyncEvent asyncEvent) {
145
                if (logger.isDebugEnabled()) {
×
146
                    logger.debug("Async Completed " + asyncEvent);
×
147
                }
148
            }
×
149

150
            @Override
151
            public void onTimeout(AsyncEvent asyncEvent) {
152
                if (!activity.isCommitted() && !activity.isExceptionRaised()) {
×
153
                    activity.setRaisedException(new ActivityTerminatedException("Async Timeout " + asyncEvent));
×
154
                } else {
155
                    logger.error("Async Timeout " + asyncEvent);
×
156
                }
157
            }
×
158

159
            @Override
160
            public void onError(AsyncEvent asyncEvent) {
161
                logger.error("Async Error " + asyncEvent);
×
162
            }
×
163

164
            @Override
165
            public void onStartAsync(AsyncEvent asyncEvent) {
166
                if (logger.isDebugEnabled()) {
×
167
                    logger.debug("Async Started " + asyncEvent);
×
168
                }
169
            }
×
170
        });
171
        asyncContext.start(() -> {
×
172
            perform(activity);
×
173
            asyncContext.complete();
×
174
        });
×
175
    }
×
176

177
    private void perform(@NonNull WebActivity activity) {
178
        ThreadContextHelper.run(getServiceClassLoader(), () -> {
×
179
            try {
180
                activity.perform();
×
181
            } catch (ActivityTerminatedException e) {
×
182
                if (logger.isDebugEnabled()) {
×
183
                    logger.debug("Activity terminated: " + e.getMessage());
×
184
                }
185
            } catch (Exception e) {
×
186
                sendError(activity, e);
×
187
            }
×
188
        });
×
189
    }
×
190

191
    private void transletNotFound(@NonNull WebActivity activity) {
192
        // Provides for "trailing slash" redirects and serving directory index files
193
        if (isTrailingSlashRedirect() &&
×
194
                activity.getRequestMethod() == MethodType.GET &&
×
195
            StringUtils.startsWith(activity.getRequestName(), ActivityContext.NAME_SEPARATOR_CHAR) &&
×
196
            !StringUtils.endsWith(activity.getRequestName(), ActivityContext.NAME_SEPARATOR_CHAR)) {
×
197
            String requestNameWithTrailingSlash = activity.getRequestName() + ActivityContext.NAME_SEPARATOR_CHAR;
×
198
            TransletRuleRegistry transletRuleRegistry = getActivityContext().getTransletRuleRegistry();
×
199
            if (transletRuleRegistry.contains(requestNameWithTrailingSlash, activity.getRequestMethod())) {
×
200
                String location;
201
                if (StringUtils.hasLength(activity.getReverseContextPath())) {
×
202
                    location = activity.getReverseContextPath() + activity.getRequestName() + ActivityContext.NAME_SEPARATOR;
×
203
                } else {
204
                    location = activity.getRequestName() + ActivityContext.NAME_SEPARATOR;
×
205
                }
206
                activity.getResponse().setHeader(HttpHeaders.LOCATION, location);
×
207
                activity.getResponse().setHeader(HttpHeaders.CONNECTION, "close");
×
208
                sendError(activity.getResponse(), HttpServletResponse.SC_MOVED_PERMANENTLY, null);
×
209
                return;
×
210
            }
211
        }
212
        try {
213
            if (!getDefaultServletHttpRequestHandler().handleRequest(activity.getRequest(), activity.getResponse())) {
×
214
                if (logger.isTraceEnabled()) {
×
215
                    logger.trace("No translet mapped for " + activity.getFullRequestName());
×
216
                }
217
                sendError(activity.getResponse(), HttpServletResponse.SC_NOT_FOUND, null);
×
218
            }
219
        } catch (Exception e) {
×
220
            logger.error(e);
×
221
            sendError(activity.getResponse(), HttpServletResponse.SC_INTERNAL_SERVER_ERROR, null);
×
222
        }
×
223
    }
×
224

225
    private void sendError(@NonNull WebActivity activity, Exception e) {
226
        Throwable t;
227
        if (activity.getRaisedException() != null) {
×
228
            t = activity.getRaisedException();
×
229
        } else {
230
            t = e;
×
231
        }
232
        logger.error("Error occurred while processing request: " + activity.getFullRequestName(), t);
×
233
        if (!activity.getResponse().isCommitted()) {
×
234
            Throwable cause = ExceptionUtils.getRootCause(t);
×
235
            if (cause instanceof RequestMethodNotAllowedException) {
×
236
                sendError(activity.getResponse(), HttpServletResponse.SC_METHOD_NOT_ALLOWED, null);
×
237
            } else if (cause instanceof SizeLimitExceededException) {
×
238
                sendError(activity.getResponse(), HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, null);
×
239
            } else if (cause instanceof MaxSessionsExceededException) {
×
240
                sendError(activity.getResponse(), HttpServletResponse.SC_SERVICE_UNAVAILABLE, MAX_SESSIONS_EXCEEDED);
×
241
            } else {
242
                sendError(activity.getResponse(), HttpServletResponse.SC_INTERNAL_SERVER_ERROR, null);
×
243
            }
244
        }
245
    }
×
246

247
    private void sendError(HttpServletResponse response, int sc, String msg) {
248
        if (logger.isDebugEnabled()) {
×
249
            ToStringBuilder tsb = new ToStringBuilder("Response");
×
250
            tsb.append("code", sc);
×
251
            tsb.append("message", msg);
×
252
            logger.debug(tsb.toString());
×
253
        }
254
        try {
255
            if (msg != null) {
×
256
                response.sendError(sc, msg);
×
257
            } else {
258
                response.sendError(sc);
×
259
            }
260
        } catch (IOException e) {
×
261
            logger.error("Failed to send an error response to the client with status code " + sc, e);
×
262
        }
×
263
    }
×
264

265
    @NonNull
266
    private String getRequestInfo(@NonNull HttpServletRequest request, String reverseContextPath,
267
                                  String requestName, MethodType requestMethod) {
268
        StringBuilder sb = new StringBuilder();
×
269
        sb.append(requestMethod).append(" ");
×
270
        if (StringUtils.hasLength(reverseContextPath)) {
×
271
            sb.append(reverseContextPath);
×
272
        }
273
        sb.append(requestName).append(" ");
×
274
        sb.append(request.getProtocol()).append(" ");
×
275
        String remoteAddr = request.getHeader(HttpHeaders.X_FORWARDED_FOR);
×
276
        if (StringUtils.hasLength(remoteAddr)) {
×
277
            sb.append(remoteAddr);
×
278
        } else {
279
            sb.append(request.getRemoteAddr());
×
280
        }
281
        return sb.toString();
×
282
    }
283

284
    private boolean checkPaused(@NonNull HttpServletResponse response) {
285
        if (pauseTimeout != 0L) {
×
286
            if (pauseTimeout == -1L || pauseTimeout >= System.currentTimeMillis()) {
×
287
                if (logger.isDebugEnabled()) {
×
288
                    logger.debug(getServiceName() + " is paused, so did not respond to requests");
×
289
                }
290
                sendError(response, HttpServletResponse.SC_SERVICE_UNAVAILABLE, "Paused");
×
291
                return true;
×
292
            } else if (pauseTimeout == -2L) {
×
NEW
293
                if (logger.isDebugEnabled()) {
×
NEW
294
                    logger.debug(getServiceName() + " is not yet started");
×
295
                }
296
                sendError(response, HttpServletResponse.SC_SERVICE_UNAVAILABLE, "Starting... Try again in a moment.");
×
297
                return true;
×
298
            } else {
299
                pauseTimeout = 0L;
×
300
            }
301
        }
302
        return false;
×
303
    }
304

305
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc