I am working with a receipt printer that has a cloud function. It commuicates with a server specification which i am implementing. It polls a URL with POST requests every x seconds and when the POST response contains a certain piece of information, the printer sends a GET request to the same to URL get the information to print.
I'm implementing the print server as a Spring Boot server and i'm getting some strange problems with the POST method that i need some help with.
My problem is that the POST requests from the printer to the server never make it to the controller. However, i am able to send a POST request from Postman to the exact same URL and it gets handled by the controller.
The URL is simply: https://www.[my-domain].com:[port-number]/cloudprint
Also, i have tried copying the controller method to another Spring (not Boot) application, running on a Tomcat instance behind Apache and there, the POST requests from the printer are handled by the controller method. I can see them in the Apache log and the Tomcat log. The polling frequency is currently at 10 seconds.
Here's what the controller looks like:
package com.[my-domain].[application-name].controller;
[a bunch of imports]
@RestController
@CrossOrigin
public class PrintController {
Logger logger = LoggerFactory.getLogger(PrintController.class);
@RequestMapping(value="/cloudprint", method=RequestMethod.POST,
headers={"Accept=application/json"})
@ResponseStatus(HttpStatus.CREATED)
public @ResponseBody String printPost() {
logger.debug("in printPost");
return "OK";
}
@RequestMapping(value="/cloudprint", method=RequestMethod.GET,
headers={"Accept=application/json"})
@ResponseStatus(HttpStatus.OK)
public @ResponseBody String printGet(HttpServletRequest request) {
logger.debug("in printGet");
return "OK";
}
@RequestMapping(value="/cloudprint", method=RequestMethod.DELETE,
headers={"Accept=application/json"})
@ResponseStatus(HttpStatus.OK)
public @ResponseBody String printDelete() {
logger.debug("in printDelete");
return "OK";
}
}
What could be causing this? What can i test to solve this issue?
---ADDED INFORMATION BELOW 2019-06-03 @13:21 cet--- Since i have a regular Spring (non-boot) application that accepts the POST requests from the printer, i am able to log the information in the incoming request. So i did that.
This is one of the POST requests from the printer, which is NOT accepted by the Spring boot oontroller:
auth type: null
content type: application/json
-- HEADERS --
Header: host : dev.[our-domain-name].com
Header: accept : */*
Header: user-agent : Cente HTTPc
Header: content-type : application/json
Header: content-length : 303
Header: connection : keep-alive
QueryString: null
-- PARAMETERS --
END
This is one of the POST requests from Postman to the exact same URL, which IS accepted by the Spring boot oontroller:
auth type: null
cotent type: application/json
-- HEADERS --
Header: content-type : application/json
Header: cache-control : no-cache
Header: Postman-Token : caf99fa1-4730-4193-aab3-c4874273661d
Header: user-agent : PostmanRuntime/7.6.0
Header: accept : */*
Header: host : dev.[our-domain-name].com
Header: accept-encoding : gzip, deflate
Header: content-length : 0
Header: connection : keep-alive
QueryString: null
-- PARAMETERS --
END
Analysis: 1. The user-agent headers differ. 2. The content-length headers differ. 3. The Postman request has three headers that the request from the cloud printer does not have. They are: cache-control, Postman-token and accept-encoding
---ADDED INFORMATION BELOW 2019-06-03 @17:56 cet--- OK, i figured out how to log the message body. It is a well-formatted json structure that is indeed 303 characters:
{"status": "29 a 0 0 0 0 0 0 0 0 0 0",
"printerMAC": "00:11:62:1b:xx:xx",
"statusCode": "200%20OK",
"printingInProgress": false,
"clientAction": null,
"display": [
{"name": "MainDisplay" ,
"status": {"connected": false}}],
"barcodeReader": [
{"name": "MainBCR" ,
"status": {"connected": false,"claimed": false}}]}
I created the corresponding classes and changed the POST method in the Boot application to this:
@RequestMapping(value="/cloudprint", method=RequestMethod.POST,
headers={"Accept=application/json"})
@ResponseStatus(HttpStatus.CREATED)
public @ResponseBody String printPost(@RequestBody PrinterPostRequest printerPostRequest,
HttpServletRequest request) {
HttpRequestLogger.log(request);
return "OK";
}
Still, it is not picking up the POST request from the printer. It picks up the Postman request and successfully marshals the request body json into the classes.
---ADDED INFORMATION BELOW 2019-06-05 @10:16 cet--- I had an idea. Using Spring RestTemplate, i sent a POST request to the Boot application with the same headers and same payload as the request the printer sends. I'm getting an org.springframework.web.client.ResourceAccessException with this message:
I/O error on POST request for "https://boot.[my-domain].com:8443/cloudprint":sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target; nested exception is javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
If you use Spring Boot 2, then it could happen because of the csrf-protection. It affects only the non-GET requests, and it's turned on by default on Spring Boot 2, but it's off in earlier versions.
So it quite well reflects your issue-description - although I don't fully understand how could it work via Postman... however there is a chance that it handles it automatically somehow...
Anyway it could worth a try:
@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
}
}
Whenever Java attempts to connect to another application over SSL (e.g.: HTTPS, IMAPS, LDAPS), it will only be able to connect to that application if it can trust it.
The way trust is handled in the Java world is that you have a keystore (typically $JAVA_HOME/lib/security/cacerts), also known as the truststore. This contains a list of all known Certificate Authority (CA) certificates, and Java will only trust certificates that are signed by one of those CAs or public certificates that exist within that keystore.
This problem is therefore caused by either a certificate that is self-signed (a CA did not sign it) or a certificate chain that does not exist within the Java truststore. It does not trust the certificate and fails to connect to the application.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With