I have subclassed BaseHTTPRequestHandler
and implemented do_GET()
, do_POST()
and do_PUT()
.
I thought that everything works fine until I put my Qt application to the test. The GET request works and the server sends an XML document with some data that the application processes. The POST request also works and is used to generate test cases through REST.
As for the PUT things are looking weird.
Here is the PUT handler on the server side:
def do_PUT(self):
"""
Processes PUT request. It can be tested using wget, curl or any
other tool with support for http PUT. Use this to send reports
about the current status of device and all of its slave devices
Example:
curl -X PUT -d "status=downloading" http://127.0.0.1:8090/so/updateProgress
"""
print('Processing PUT request...')
params_parsed = urlparse(self.path)
params = parse_qs(params_parsed.query)
if len(params) > 0 or '/so/updateProgress' not in params_parsed.path:
print('Use "/so/updateProgress" to report on update progress')
self.__set_headers(data_type='text/html')
self.send_error(501)
return
self.__set_headers(data_type='text/xml')
report_raw = self.rfile.read(int(self.headers.getheader('Content-length')))
print('Received report')
print('Parsing report...')
# Generate XML from the raw data and validate it
report = SoRequestHandler.Report.from_xml(report_raw)
print('Parsing of report complete')
report.write_to_file(log_path='report_log', append=True)
self.send_response(200)
In my Qt application I have a class called ReportPublisher
, which takes some reports from all the devices it is connected to (through MQTT), aggregates the reports into a single one and sends it to the server, which logs it in a file:
void ReportPublisher::publishHttpReport(HttpReport report)
{
QString reportXml = report.xml().toString();
if (reportXml.isEmpty())
{
LOG(ERROR) << "Something went wrong with the generation of HTTP report";
return;
}
LOG(INFO) << "Generated report for SO server: " << reportXml;
QByteArray reportRaw = reportXml.toUtf8();
QUrl target(this->urlServer);
target.setPath(this->urlPath);
QNetworkRequest req(target);
req.setHeader(QNetworkRequest::ContentTypeHeader, QString("application/xml"));
this->reply = this->nam->put(req, reportRaw);
// TODO Read back response (expected code 200)?
}
I have to be honest. I have never done a PUT request in Qt and all I've done so far were GET requests so there might be some really fundamental yet easy to spot error in the code above.
The data that the server receives looks like this:
<updateProgress xmlns='service.so.de/so'>
<deviceTypeId>...</deviceTypeId>
<packageId>...</packageId>
<version>...</version>
<status>...</status>
</updateProgress>
If I use curl
like this
root@obunit:~$ curl -X PUT -i 'http://192.168.120.61:8090/so/updateProgress' -d "<updateProgress xmlns='service.so.de/so'><deviceTypeId>...</deviceTypeId><packageId>...</packageId><version>...</version><status>...</status></updateProgress>"
where 192.168.120.61:8090 is the IP address and the port the server is located at/listening to for incoming requests I have no problem.
However with data coming from my Qt application I get
192.168.120.172 - - [11/Apr/2018 15:32:37] code 400, message Bad HTTP/0.9 request type ('PUT')
192.168.120.172 - - [11/Apr/2018 15:32:37] "PUT HTTP/1.1" 400 -
in my log (with 192.168.120.172 being the IP address of the system where my software is running.
From my scarce knowledge code 400 means invalid syntax, which can be due to following 2 reasons (at least what I can think of right now):
I have tried converting the XML document that I'm generating to QByteArray
using QDomDocument::toByteArray(int i)
. I have also tried (as you can see in the code) to convert the document to QString
and then to a UTF-8 QByteArray
but I can't get my server to process the data.
What's even stranger is that (as you can see in my do_PUT()
) implementation my PUT handler starts with printing a message, which never appears in the logs hence the issue arises from somewhere deeper in the code of BaseHTTPRequestHandler
.
The problem was rather simple yet very annoying.
Following the advice of @MrEricSir I did use Wireshark just to find out that get some weird TCP retramsission. Retransmission usually occurs due to network congestion, which in my case was not a possibility however it did indicate a network issue that has nothing to do with the XML content I was sending.
So I started my remote debugging session again and look closer to where I was emitting the PUT request. And there it was! There was a /
missing between the base URL (IP:PORT) and the path. So instead of
PUT 192.168.120.61:8090/so/updateProgress
I was doing
PUT 192.168.120.61:8090so/updateProgress
which ultimately led to the PUT request not succeeding at all (hence the code 400).
Just a hint for anyone reading this in the future - whenever you manually set that path using QUrl::setPath(QString)
QUrl target(this->urlServer);
target.setPath(this->urlPath);
ALWAYS make sure that the string you pass as a path starts with /
because Qt isn't going to add one!