SAP / cloud-sdk

The SAP Cloud SDK documentation and support repository.
https://sap.github.io/cloud-sdk/
Apache License 2.0
45 stars 43 forks source link

Async Servlet in Java Cloud SDK #972

Open Mapobo opened 2 years ago

Mapobo commented 2 years ago

Issue Description

Important information:

Hi all! I am trying to create an asynchronous service to generate document's employees with sap cloud sdk for neo environment.

The process is the following:

First an external application sends a post request with a body similar to this:

{ "Empleados": ["XXXX", "YYYY", "EEEEE"], "Secciones": ["1", "2", "3", "4", "5", "6","7","9","10","11","12","13","14","15","16"] }

Empleados contains the employees reference and Secciones contains the sections to print.

Then the service returns the following body :

{
    "Total": 2,
    "Procesados": 0,
     "Empleados": ["XXXX", "YYYY", "EEEEE"],
    "Secciones": ["1", "2", "3", "4", "5", "6","7","9","10","11","12","13","14","15","16"]
    "UUID": "3315ac6e-fa10-4ccf-a4f6-d6eb7452787a",
    "Success": false,

}

Then, a background process generates documents and saves the json string info and a zip file with documents in two differents attributes of session object to be obtained later (once all documents are generated). Is it correct to save this info in attribute of session Object? this.session.setAttribute(this.uniqueIdpdf, ByteArrayOutputStream);

An UUID is generated by the service to identify the request and other properties (success -> process finished flag, total -> total employee/docs to generate, procesados -> count of documents generated).

In the meantime, the external App call every second the service with the following get request (you can review the UUID parametter to identify the process) GetCvs?UUID=3315ac6e-fa10-4ccf-a4f6-d6eb7452787a

and retrieve the same json info but the property "Procesados" increase while the generator process gen docs.

The only way to make this process works It is adding a new thread to execute it in background.

Rigth now the following code is working correctly.

new ThreadContextExecutor().execute(() -> {
            new Thread() {
                public void run() {

                    System.out.println("Hilo Async:" + Thread.currentThread().getName());

                    try {
                        cvManager.init();
                    } catch (Exception e) { // TODO: handle exception
                        e.printStackTrace();
                    }

                }
            }.start();
        });

After I tested a similar code with normal servlet (no created with sdk) and apache tomcat server 7.0 . I could use async support and created a asyncContext similar to this.

` final AsyncContext asyncContext = request.startAsync(request, response);

    asyncContext.start(new Runnable() {
        public void run() {

            try {
                cvManager.init();
            } catch (Exception e) {
                // TODO: handle exception
                e.printStackTrace();
            }

        }

    });

    try {
        response.getWriter().print(jsonObject.toString());
        response.getWriter().close();
    } catch (IOException e1) {
        // TODO Auto-generated catch block
        e1.printStackTrace();
    }`

However, I tried to use asyncContext with sdk servlet and the following error is displayed. com.sap.cloud.sdk.cloudplatform.thread.exception.ThreadContextExecutionException: java.lang.IllegalStateException: A filter or servlet of the current chain does not support asynchronous operations..

I tried to add assync-supported in @WebServlet and in web.xml but always appear this error. Exist some incompatibility with sdk and asynchronous process?

I saw Multitenancy, Thread Context and async Operations in the documentation and i tried to implement that but always execute in same thread and the post request return body when all docs are generated.

`ThreadContextExecutor executor = new ThreadContextExecutor(); Callable operationWithContext = () -> executor.execute(() -> operation());

invokeAsynchronously(operationWithContext);`

And I dont find or understand invokeAsyncronously.

What Is the better way to implement this aproach?

Sorry for my java knowledge. I new in that.

Thank you for your help.

Impact / Priority

Affected development phase: e.g. Getting Started, Development, Release, Production

Impact: e.g. No Impact, Inconvenience, Impaired, Blocked

Timeline: e.g. Go-Live is in 12 weeks.

Error Message

Error Output when try used request.startAsync

14:23:59.951 [localhost-startStop-1] INFO  c.s.c.s.s.c.soap.Axis2CustomConverterListener - Axis2 Custom Converter Class SoapCustomConverter registered during startup.
14:24:23.339 [localhost-startStop-2] INFO  c.s.c.s.s.c.soap.Axis2CustomConverterListener - Axis2 Custom Converter Class SoapCustomConverter registered during startup.
14:25:34.043 [http-nio-8080-exec-7] INFO  com.inetum.neo.async.gen.pdf.GetCvs - Hilo Principal:http-nio-8080-exec-7
14:25:34.061 [http-nio-8080-exec-7] WARN  c.s.c.s.c.servlet.RequestAccessorFilter - Unexpected servlet filter exception: java.lang.IllegalStateException: A filter or servlet of the current chain does not support asynchronous operations.
com.sap.cloud.sdk.cloudplatform.thread.exception.ThreadContextExecutionException: java.lang.IllegalStateException: A filter or servlet of the current chain does not support asynchronous operations.
        at com.sap.cloud.sdk.cloudplatform.thread.AbstractThreadContextExecutor.execute(AbstractThreadContextExecutor.java:352)
        at com.sap.cloud.sdk.cloudplatform.servlet.RequestAccessorFilter.doFilter(RequestAccessorFilter.java:66)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at com.sap.cloud.sdk.cloudplatform.security.servlet.HttpCachingHeaderFilter.doFilter(HttpCachingHeaderFilter.java:87)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at com.sap.cloud.sdk.cloudplatform.security.servlet.HttpSecurityHeadersFilter.doFilter(HttpSecurityHeadersFilter.java:45)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
        at org.apache.tomee.catalina.OpenEJBValve.invoke(OpenEJBValve.java:44)
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:543)
        at com.sap.cloud.runtime.impl.bridge.security.AbstractAuthenticator.invoke(AbstractAuthenticator.java:236)
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)
        at org.apache.tomee.catalina.OpenEJBSecurityListener$RequestCapturer.invoke(OpenEJBSecurityListener.java:97)
        at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:698)
        at com.sap.cloud.runtime.kotee.jta.cleaner.LeakedTransactionsReportValve.invoke(LeakedTransactionsReportValve.java:155)
        at com.sap.core.tenant.valve.TenantValidationValve.invokeNextValve(TenantValidationValve.java:182)
        at com.sap.core.tenant.valve.TenantValidationValve.invoke(TenantValidationValve.java:97)
        at com.sap.js.statistics.tomcat.valve.RequestTracingValve.callNextValve(RequestTracingValve.java:113)
        at com.sap.js.statistics.tomcat.valve.RequestTracingValve.invoke(RequestTracingValve.java:59)
        at com.sap.core.js.monitoring.tomcat.valve.RequestTracingValve.invoke(RequestTracingValve.java:27)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:367)
        at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:639)
        at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
        at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:882)
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1691)
        at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
        at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
        at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.IllegalStateException: A filter or servlet of the current chain does not support asynchronous operations.
        at org.apache.catalina.connector.Request.startAsync(Request.java:1726)
        at org.apache.catalina.connector.Request.startAsync(Request.java:1718)
        at org.apache.catalina.connector.RequestFacade.startAsync(RequestFacade.java:1031)
        at javax.servlet.ServletRequestWrapper.startAsync(ServletRequestWrapper.java:386)
        at com.inetum.neo.async.gen.pdf.GetCvs.doPost(GetCvs.java:107)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:681)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:764)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.apache.openejb.server.httpd.EEFilter.doFilter(EEFilter.java:65)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at com.sap.cloud.sdk.cloudplatform.servlet.RequestAccessorFilter.lambda$doFilter$0(RequestAccessorFilter.java:66)
        at com.sap.cloud.sdk.cloudplatform.thread.AbstractThreadContextExecutor.lambda$execute$1(AbstractThreadContextExecutor.java:344)
        at com.sap.cloud.sdk.cloudplatform.thread.ThreadContextCallable.call(ThreadContextCallable.java:229)
        at com.sap.cloud.sdk.cloudplatform.thread.AbstractThreadContextExecutor.execute(AbstractThreadContextExecutor.java:346)
        ... 35 common frames omitted
14:25:34.063 [http-nio-8080-exec-7] ERROR o.a.c.c.C.[.[.[.a.gen.pdf-application].[GetCvs] - Servlet.service() para servlet [GetCvs] lanz¾ excepci¾n
com.sap.cloud.sdk.cloudplatform.exception.ShouldNotHappenException: com.sap.cloud.sdk.cloudplatform.thread.exception.ThreadContextExecutionException: java.lang.IllegalStateException: A filter or servlet of the current chain does not support asynchronous operations.
        at com.sap.cloud.sdk.cloudplatform.servlet.RequestAccessorFilter.doFilter(RequestAccessorFilter.java:70)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at com.sap.cloud.sdk.cloudplatform.security.servlet.HttpCachingHeaderFilter.doFilter(HttpCachingHeaderFilter.java:87)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at com.sap.cloud.sdk.cloudplatform.security.servlet.HttpSecurityHeadersFilter.doFilter(HttpSecurityHeadersFilter.java:45)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
        at org.apache.tomee.catalina.OpenEJBValve.invoke(OpenEJBValve.java:44)
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:543)
        at com.sap.cloud.runtime.impl.bridge.security.AbstractAuthenticator.invoke(AbstractAuthenticator.java:236)
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)
        at org.apache.tomee.catalina.OpenEJBSecurityListener$RequestCapturer.invoke(OpenEJBSecurityListener.java:97)
        at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:698)
        at com.sap.cloud.runtime.kotee.jta.cleaner.LeakedTransactionsReportValve.invoke(LeakedTransactionsReportValve.java:155)
        at com.sap.core.tenant.valve.TenantValidationValve.invokeNextValve(TenantValidationValve.java:182)
        at com.sap.core.tenant.valve.TenantValidationValve.invoke(TenantValidationValve.java:97)
        at com.sap.js.statistics.tomcat.valve.RequestTracingValve.callNextValve(RequestTracingValve.java:113)
        at com.sap.js.statistics.tomcat.valve.RequestTracingValve.invoke(RequestTracingValve.java:59)
        at com.sap.core.js.monitoring.tomcat.valve.RequestTracingValve.invoke(RequestTracingValve.java:27)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:367)
        at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:639)
        at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
        at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:882)
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1691)
        at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
        at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
        at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:748)
Caused by: com.sap.cloud.sdk.cloudplatform.thread.exception.ThreadContextExecutionException: java.lang.IllegalStateException: A filter or servlet of the current chain does not support asynchronous operations.
        at com.sap.cloud.sdk.cloudplatform.thread.AbstractThreadContextExecutor.execute(AbstractThreadContextExecutor.java:352)
        at com.sap.cloud.sdk.cloudplatform.servlet.RequestAccessorFilter.doFilter(RequestAccessorFilter.java:66)
        ... 34 common frames omitted
Caused by: java.lang.IllegalStateException: A filter or servlet of the current chain does not support asynchronous operations.
        at org.apache.catalina.connector.Request.startAsync(Request.java:1726)
        at org.apache.catalina.connector.Request.startAsync(Request.java:1718)
        at org.apache.catalina.connector.RequestFacade.startAsync(RequestFacade.java:1031)
        at javax.servlet.ServletRequestWrapper.startAsync(ServletRequestWrapper.java:386)
        at com.inetum.neo.async.gen.pdf.GetCvs.doPost(GetCvs.java:107)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:681)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:764)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.apache.openejb.server.httpd.EEFilter.doFilter(EEFilter.java:65)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at com.sap.cloud.sdk.cloudplatform.servlet.RequestAccessorFilter.lambda$doFilter$0(RequestAccessorFilter.java:66)
        at com.sap.cloud.sdk.cloudplatform.thread.AbstractThreadContextExecutor.lambda$execute$1(AbstractThreadContextExecutor.java:344)
        at com.sap.cloud.sdk.cloudplatform.thread.ThreadContextCallable.call(ThreadContextCallable.java:229)
        at com.sap.cloud.sdk.cloudplatform.thread.AbstractThreadContextExecutor.execute(AbstractThreadContextExecutor.java:346)
        ... 35 common frames omitted

Project Details


Checklist

MatKuhr commented 2 years ago

Hi Mapobo, thanks for reaching out!

You are correct, the SDK filters currently don't support async. You could work around this by writing a custom filter that does it, but it is a lot of custom code and I'm not certain that it will work on Neo.

Code ```Java import com.sap.cloud.sdk.cloudplatform.exception.ShouldNotHappenException; import com.sap.cloud.sdk.cloudplatform.servlet.RequestThreadContextListener; import com.sap.cloud.sdk.cloudplatform.thread.ThreadContext; import com.sap.cloud.sdk.cloudplatform.thread.ThreadContextExecutor; import com.sap.cloud.sdk.cloudplatform.thread.ThreadContextListener; import io.vavr.control.Option; import java.util.List; import javax.annotation.Nonnull; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Servlet filter for setting the request context for cloud SDK. */ @WebFilter( filterName = "CustomRequestAccessorFilter", urlPatterns = {"/*"}, asyncSupported = true ) public class CustomRequestAccessorFilter implements Filter { private static final Logger log = LoggerFactory.getLogger(CustomRequestAccessorFilter.class); public CustomRequestAccessorFilter() { } public void init(@Nonnull FilterConfig filterConfig) { } /** * Add request to filter chain. */ public void doFilter(@Nonnull ServletRequest request, @Nonnull ServletResponse response, @Nonnull FilterChain filterChain) { if (request instanceof HttpServletRequest) { try { ThreadContextExecutor threadContextExecutor = new ThreadContextExecutor(); List listeners = threadContextExecutor .getListenersOrderedByPriority(); Option existing = Option.ofOptional( listeners.stream().filter(listener -> listener.getPriority() == -8).findAny()); if (!existing.isDefined()) { log.info("Adding request via thread context listener"); threadContextExecutor.withListeners( new RequestThreadContextListener((HttpServletRequest) request)); } else if (log.isWarnEnabled()) { log.warn("Failed to add " + ThreadContextListener.class.getSimpleName() + ": listener " + (existing.get()).getClass().getName() + " with priority " + -8 + " already exists."); } threadContextExecutor.execute(() -> filterChain.doFilter(request, response)); } catch (Throwable var7) { log.warn("Unexpected servlet filter exception: " + var7.getMessage(), var7); throw new ShouldNotHappenException(var7); } } else if (log.isWarnEnabled()) { log.warn( "Failed to initialize " + ThreadContext.class.getSimpleName() + ": request not of type " + HttpServletRequest.class.getName() + "."); } } public void destroy() { } } ```

Could you elaborate on what SDK functionality you want to use inside the async operation? Because currently the SDK context will anyway be destroyed when the initial filter chain ends, i.e. when the initial request is processed & answered. Thus, passing it on to a long-living thread doesn't really work. We are working on an improvement to make this work, but I can't give a timeline yet on when that will be available, and if it will work on Neo..

How to use invokeAsynchronously `invokeAsynchronously` is just a "placeholder", you can fill it with anything that runs a piece of code asynchronously. For example, you could use `Executors.newCachedThreadPool().submit(operationWithContext)`. Or, if you directly want to spawn a Thread, like in your example above: ``` ThreadContextExecutor executor = new ThreadContextExecutor(); new Thread() { public void run() { System.out.println("Hilo Async:" + Thread.currentThread().getName()); try { executor.execute(() -> cvManager.init()) } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } } }.start(); ``` Note that this code is calling `execute` inside of the new thread, not outside of it like in your example above.
Mapobo commented 2 years ago

I think I have misunderstood the concept of thread context. Reviewing the documentation the thread context saves the tenant, the user and the JSON JWT token. In my asynchronous process I use a destination to get the data but with the technical user and thread context it is not necessary (principal propagation is not necessary). So I understand that it is not necessary to use the thread context. is it correct ? I saw asynchronous process and try to implement this hahaha.

What do you think about using in new thread instead of creating the asynchronous filter? I have read that using new thread in servlet is discouraged because of memory access issues.

Do you think saving the zip file and json information as an attribute on the session object is adequate?

MatKuhr commented 2 years ago

Okay, so when using a destination the thread context may or may not be needed. It depends if your destination is related to a specific tenant or user. Is this the case, or is your destination defined on the "provider tenant" i.e. the account you are running the app with? In the provider case, the thread context should not be needed.

I have read that using new thread in servlet is discouraged because of memory access issues.

Yes, because threads may be re-used so you should clean up any state you are creating on the thread. Also, accessing the request of the servlet from another Thread is a problem, because that may also be recycled.

I'd think that if you have some "simple" logic that doesn't do these things you should be fine, but for sure you would have to read up on that and cross check with your implementation that there are no side effects.

Do you think saving the zip file and json information as an attribute on the session object is adequate?

I don't really know. If you store it on the session, it will have the same lifecycle as the session. That may depend on your configuration, how long those last. Also, in case your app crashes or a re-login happens you may lose the data. Not sure how impactful that is. E.g. if the computation takes a long time you may want to store the data in a database instead in order to recover in a case of failure.

Also, I'm not sure what the exact interaction between async threads and sessions are. E.g. you may have to ensure that there is no race condition, where two threads write to the same session at the same time.

Mapobo commented 2 years ago

Hello again.

First of all thanks for your help.

I have done a series of "stress" tests with multiple calls at the same time and the service works fine :)

At the end I have called the asynchronous functionality with the code you suggested as an example.

ExecutorService executor = Executors.newCachedThreadPool();

        executor.submit(new Runnable() { 

            @Override
            public void run() {
                logger.info("doPost:: Hilo Async:" + Thread.currentThread().getName());

                try {
                    logger.info("doPost:: Generando CVs");
                    cvManager.init();
                } catch (Exception e) { 
                    e.printStackTrace();
                }

                executor.shutdown();
            }
        });

The information saved in the session only remains there for a few seconds, (when the second call is called it comes back and is deleted). It is not possible to overwrite the information because one uuid is generated per call/file.

I think we are on the right track But I have another question.

Is it possible to use in neo a standard servlet (without sdk) and startAsync functionality? In this case i wouldn´t use the destination but i could call directly the provider data service . It would be possible?

Thanks for all.

MatKuhr commented 2 years ago

Happy to hear that it works for your use case! WRT the async servlet support, this seems to be supported: https://answers.sap.com/questions/9831334/servlet-30-async-support.html

Maybe just give it a try :)

jensmatw commented 2 years ago

Not sure if I should open a new issue, but since you already talk about async here, I'll give it a try: We also faced an async issue, we needed the cloud-sdk to be async-compatible so that we can work with JWT tokens in async servlets for server-side-events. For our surprise, all we had to do is remove the cloud-sdk package from componentscan and provide a copy of your RequestAccessorFilter with the only change, that we set asyncSupported to true: @WebFilter(filterName = "RequestAccessorFilter", urlPatterns = "/*", asyncSupported = true).

It seems that at least this filter works fine asyncronously. If you think there is no other problem that we maybe have overseen, could this be added to the sdk diredctly?

Mapobo commented 2 years ago

This is very good news, could you please detail the process? I hope they can be added to the SDK soon.

Thanks to all of you.

jensmatw commented 2 years ago

I've just added asyncSupported = true to the WebFilter annotation in RequestAccessorFilter.

The code in RequestAccessorFilter seems to be working fine asynchronously, the filter is just rejected in async servlets as long as asyncSupported is not set to true. At least for our server-side-events, we can access the auth details in the thread context without issues.

newtork commented 2 years ago

Hi colleagues,

I'm the product owner of SAP Cloud SDK. We're planning to address the async support for our servlet Filter soon in Q4/2022. The reason for not immediately fixing this, is that we need the time to check all potential use-cases. If we miss something here, then we could potentially silently break SAP Cloud SDK for other customers. For today a workaround is available.

We will also update this ticket as soon as there is an update :+1:

Kind regards Alexander