2

We have an eCommerce webapp with the front-end server and the API server being in separate EC2 Ubuntus. The API server exposes 168 API RESTFul endpoints using Spring MVC. Each class has the @RestController annotation. We have a problem with one of them.

When this endpoint (which is a PUT) is called by the front-end, we know that it is invoked in Spring, we see its effects in the database and the PDF file it generates, but then (every single time) it returns 404 to the browser which we see in the tomcat access logs, as well, even though it is invoked every time.. There is nothing extraordinary about this endpoint. All 168 endpoints look the same. They have the HTTP method, produces JSON and return ResponseEntity. I moved this endpoint to another new class, I changed its endpoint URI, I changed the method name, I removed everything from this method except for "return ResponseEntity.ok().body(new SomeObject())", but it always returns 404. Does Spring MVC do this or tomcat itself?

We use OpenJDK 11.0.11+9, tomcat 9.0.59, Spring Framework 5.3.16, Spring Security 5.6.2, javaee-api 8.0.1 and servlet 4.0. Here is the endpoint method:

@RestController
@RequestMapping(value = "/api/v1/drivers")
public class DriverAPIService
{
    @PutMapping(value = "/delivered", produces = "application/json")
    public ResponseEntity<OrderStatusResponseBean> markOrderAsDelivered(@RequestBody OrderStatusUpdateRequestBean requestBean, HttpServletRequest request, HttpServletResponse response, Model model)
    {
        try
        {
             return DriverService.markOrderAsDelivered(requestBean, request);
        }
        catch(Throwable e)
        {
            return ResponseEntity.badRequest().build();
        }
    }
}

Our tomcat config in the server.xml for the connectors is:

<Executor name="tomcatThreadPool" namePrefix="tomcat9-thread-" maxIdleTime="60000"
maxThreads="50" minSpareThreads="5"/>

<Connector executor="tomcatThreadPool" port="8080" protocol="HTTP/1.1" redirectPort="8443" maxConnections="100" acceptCount="300"/>

<Connector executor="tomcatThreadPool" SSLEnabled="true" maxPostSize="5000000" port="8443" protocol="org.apache.coyote.http11.Http11Nio2Protocol" scheme="https"
    secure="true" compression="on" maxKeepAliveRequests="10" connectionTimeout="60000"
    maxConnections="100" acceptCount="300" enableLookups="false" connectionUploadTimeout="120000" disableUploadTimeout="false">
    <SSLHostConfig>
       <Certificate certificateFile="conf/cert.pem" certificateKeyFile="conf/privkey.pem" certificateChainFile="conf/chain.pem" />
    </SSLHostConfig>

    <UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" compression="on" keepAliveTimeout="360000"/>
</Connector>
General Grievance
  • 4,555
  • 31
  • 31
  • 45
Dimitrios Efthymiou
  • 535
  • 1
  • 5
  • 17
  • In your method, what is the purpose of the `HttpServletResponse` and `Model` arguments? Please, could you outline the process performed by `DriverService.markOrderAsDelivered`? Does Tomcat report any errors in the logs? – jccampanero Mar 21 '22 at 22:16
  • the HttpServletResponse and Model arguments are not used in this method. They just exist and Spring MVC always injects them. The markOrderAsDelivered() updates the order status in the database to "DELIVERED" and generates the invoice PDF for that order which gets uploaded to AWS S3. No errors in Tomcat. The operation completes from start to finish. This endpoint always gets invoked, but tomcat returns 404 – Dimitrios Efthymiou Mar 22 '22 at 10:09
  • Can you add the DriverService.markOrderAsDelivered(requestBean, request); method to check what is being done there? Also do you have anything configured differently for that endpoint in WebSecurityConfigurerAdapter? – pringi Mar 22 '22 at 15:44
  • the markOrderAsDelivered() just updates the database record for the order and generates the invoice PDF using the com.spire.doc and iText libraries. We use the same libraries on other endpoints. That code is about 450 lines spanning 14 classes. I cannot just copy/paste it here. I even removed all the contents and just returned 200 (OK). I just kept 1 line in there, but it still returns 404 – Dimitrios Efthymiou Mar 22 '22 at 16:55
  • Thank you for the feedback @DimitriosEfthymiou. You mentioned in your comments that even with one line the error is still there. Please, could you indicate how does the object you are returning look like? Does it work if you just `return ResponseEntity.ok().build()`? My guess is that it should be some error when returning your data, perhaps when dealing with serialization, and it is causing a redirection to some error page/route, which actually does not exists? If you inspect your requests in the browser inspector, is there any valuable information? – jccampanero Mar 22 '22 at 22:38
  • I only returned return ResponseEntity.ok().build(). Yes. And still error. There is no serialisation, I guess, of data since I return only 200 (OK). In the browser we just see 404. In the tomcat logs I just see 404. This endpoint worked for a year and suddenly it started returning 404 2 weeks ago. I did not change this endpoint or the Spring or tomcat config – Dimitrios Efthymiou Mar 22 '22 at 23:30

1 Answers1

0

Let's recapitulate what we have:

  • Your service code is wrapped into a catch-all block, that will transform any inner exceptions into an http-400 response. Catch-alls are a code smell, but in this case it helps to exclude the possibility that the problem is deep in your service.
  • The service code is executed (as you say), so the method lookup generally works.

IMHO the problem must be in the response processing. I suspect the HttpServletResponse to be the problem here. There are only rare cases when it's really needed (streaming, cookies, etc.), but generally it causes more problems than it actually solves. So please remove any unneeded parameters from the method signature and try again.

oliver_t
  • 968
  • 3
  • 10
  • unfortunately it still returns 404 even though it executes – Dimitrios Efthymiou Mar 22 '22 at 10:32
  • Is the HttpServletRequest somehow manipulated in your service code? Do you really need the complete object? Can't you just hand in single parameters or headers? – oliver_t Mar 22 '22 at 16:26
  • Yes. The HttpServletRequest is manipulated. I have a JWT filter and during that filter I save in the request object the userID (extracted from that JWT) using request.setAttribute("current_user_ID", tokenData.getUserID()); Then every API endpoint just calls getUserID(request) to extract that userID using request.getAttribute("current_user_ID"); – Dimitrios Efthymiou Mar 22 '22 at 17:01
  • Hmm, this sounds unsuspicious. Ok, one last try. Let's further reduce the response processing: Can you please change the method to return a void (=HTTP 200 without body) and try again? And maybe change the signature and set the HttpServletRequest to be the first parameter? – oliver_t Mar 23 '22 at 09:46
  • no. Still the same. WOW!!!!!!! – Dimitrios Efthymiou Mar 23 '22 at 10:07
  • Really Strange... Futher ideas: Remove your whole method body and replace it by just a simple log statement. Turn off all security. Remove all other controllers. BTW: How do call your endpoints? Maybe write a MockMvc test and strip the Spring context down to a minimum. – oliver_t Mar 23 '22 at 11:05