QNetworkRequest with headers - qt

I am using Qt to interact with a GraphQL host. I am using QNetworkRequest, and the login process is done well, gives me a token in JSON format. Since then, GraphQL expects the token to be in the HTTP header:
{
"Authorization": "Bearer <token>"
}
Testing the server, I wrote a small Python code, which works well:
headers = {'Authorization': "Bearer {0}".format(token)}
url = "http://example.com:8000/graphql"
params = {'query': '{fieldQueries{fields(userId:"xxx"){fieldName fieldID}}}'}
result = requests.post(url, params=params, headers=headers)
print(result.json())
The below code is supposed to do the same operation in Qt:
QUrl url = QUrl("http://example.com:8000/graphql");
QNetworkAccessManager * mgr = new QNetworkAccessManager(this);
QNetworkRequest request(url);
QString query = QString("{fieldQueries{fields(userId:\"%1\"){fieldName fieldID}}}").arg(userId);
QUrlQuery params;
params.addQueryItem("query", query);
connect(mgr, SIGNAL(finished(QNetworkReply*)), this, SLOT(onQueryFinish(QNetworkReply*)));
connect(mgr, SIGNAL(finished(QNetworkReply*)), mgr, SLOT(deleteLater()));
auto header = QString("Bearer %1").arg(token);
request.setRawHeader(QByteArray("Authorization"), header.toUtf8());
mgr->post(request, params.query().toUtf8());
However, server gives back an internal server error (500).
As soon as I comment out the request.setRawHeader, server gives back You are not authorized to run this query.\nNot authenticated with no error.
How to make Qt to send this header correctly?
I don't know if it helps, but I checked the packets using WireShark. The Python generated packet for this request is a single packet (around 750 bytes), though Qt request is broken into two packets which the length of the first is 600 byte.
The working package:
...POST /graphql?query=%7BfieldQueries%7Bfields%28userId%3A%22xxx%22%29%7BfieldName+fieldID%7D%7D%7D
HTTP/1.1..Host: xxx:8000..
User-Agent: python-requests/2.21.0..
Accept-Encoding: gzip, deflate..
Accept: */*..
Connection: keep-alive..
Content-Type: application/json..
Authorization: Bearer <688 bytes token>..Content-Length: 0....
Qt generated packages:
....POST /graphql HTTP/1.1..Host: xxx:8000..
Authorization: Bearer <364 bytes token>..
Content-Type: application/json..
Content-Length: 113..
Connection: Keep-Alive..
Accept-Encoding: gzip, deflate..
Accept-Language: en-US,*..
User-Agent: Mozilla/5.0....
and
.e..query=%7BfieldQueries%7Bfields(useId:%22xxx%22)%7BfieldName fieldID%7D%7D%7D
I have checked other given solutions for the header, such as
Correct format for HTTP POST using QNetworkRequest,
Sending HTTP Header Info with Qt QNetworkAccessManagerand read the
QNetworkRequest Class documentation.

Graphql accepts both POST an GET requests. Therefore, instead of post, I used GET. It sends the query as a part of the URL, not the header, which from the captured packets I realized they are sent as the body.
The solution is as follows:
QNetworkAccessManager * mgr = new QNetworkAccessManager(this);
QString query = QString("{fieldQueries{fields(userId:\"%1\"){fieldName fieldID}}}").arg(userId);
QUrl url = QUrl(QString("http://example.com:8000/graphql?query=%1").arg(query));
QNetworkRequest request(url);
connect(mgr, SIGNAL(finished(QNetworkReply*)), this, SLOT(onQueryFinish(QNetworkReply*)));
connect(mgr, SIGNAL(finished(QNetworkReply*)), mgr, SLOT(deleteLater()));
auto header = QString("Bearer %1").arg(token);
request.setRawHeader(QByteArray("Authorization"), header.toUtf8());
mgr->get(request);

Related

POST Request with WiFiNina Library on Arduino Uno Wifi Rev 2

I'm trying to write some code for the Arduino Uno WiFi Rev2 board. Specifically, I'd like to send some data, using a POST HTTP request, to an API Endpoint that I wrote. I've tested this endpoint with Postman, and it works fine. However, when I try POSTing my data using the WiFiNina library (https://www.arduino.cc/en/Reference/WiFiNINA), the request never makes it to my endpoint.
My Arduino sketch consists of two files. The first is my "main" file that serves as the entrypoint for my code and deals with most of my code's functionality. In this file, I setup the WiFiClient as instructed in Arduino's documentation:
#define URL "myappdomain.azurewebsites.net"
...
WiFiClient client;
int status = WL_IDLE_STATUS;
...
void setup(){
...
status = WiFi.begin(WifiSSID, WifiPassword);
while (WiFi.status() != WL_CONNECTED) { //Wait for the WiFI connection completion
delay(500);
Serial.println("Waiting for connection");
}
...
}
...
void loop(){
...
String requestBody =
"{\n \"clientReadings\": {\n \"sensorA\": [],\n \"sensorB\": []\n },\n \"deviceId\": 1,\n \"millilitersConsumed\" : 999\n}";
//send the request
postData(requestBody);
...
}
In my second file, here's the part of my code that handles this API request:
void postData(String body){
if(WiFi.status()== WL_CONNECTED){ //Check WiFi connection status
if (client.connect(URL, 80)){
client.println("POST /api/PGWC HTTP/1.1");
client.print("Host: ");
client.println(URL);
client.println("Content-type: application/json");
client.println("Accept: */*");
client.println("Cache-Control: no-cache");
client.print("Host: ");
client.println(URL);
client.println("Accept-Encoding: gzip, deflate");
client.print("Content-Length: ");
client.println(body.length());
client.println("Connection: close");
client.println();
client.println(body);
}else{
Serial.println("Error connecting to server");
}
}
}
I've structured this request based off examples and documentation I've found online for the WiFiNina library. (Here's one example: https://www.arduino.cc/en/Reference/WiFiNINAClient). The header information and the body are based off the requests I've sent through Postman, so I believe that the content of my request is accurate. I believe that I'm able to connect to the server via the "client.connect" line because I never see the error message printed to the Serial monitor, but I have seen the Serial Monitor display the contents of "Serial.println" statements that I placed prior to my "client.println" statements. However, the Azure Function App that hosts the API endpoints shows no indication that the API endpoint was hit. When I send this same data with Postman, the function app logs the connection just fine.
When I try printing the contents of the body, URL, and body.length() to the Serial monitor from within the postData function, everything appears as expected. Further, I've tried different options on the "Connection: close" line to no avail.
For reference, here's the content that Postman tells me it's sending when it successfully hits the API endpoint. I've also tried port 443. It works fine in Postman, but, again, not in the Arduino.
Host: myappdomain.azurewebsites.net:80
Content-Type: application/json
User-Agent: PostmanRuntime/7.17.1
Accept: */*
Cache-Control: no-cache
Postman-Token: [some UUID, some other UUID]
Host: myappdomain.azurewebsites.net:80
Accept-Encoding: gzip, deflate
Content-Length: 130
Connection: keep-alive
cache-control: no-cache
{
"clientReadings": {
"sensorA": [],
"sensorB": []
},
"deviceId": 1,
"millilitersConsumed" : 123
}```
(I've changed the domain for this post, so this request won't work if you try to plug it into Postman because myappdomain.azurewebsites.net is not my real domain)
EDIT: figured it out.
turns out wifi.println("Content-Length: " + body.length());
is not the same as wifi.println("Content-Length: " + String(body.length()));
the body length var needs to be wrapped as a string then it works fine
What confused me was seeing the OP write it as
wifi.print("Content-Length: ");
wifi.println(body.length());
I did not think it needed to be wrapped as a String, but I guess when concatenating strings its different.
Also it was curious I needed this header to begin with (the REST app didn't require it to work) and to have a correct body value, using something like 200 caused the connection to hang (but on the REST app setting it to something generic like 200 worked fine as well)
END EDIT ...leaving original content below for reference
I am trying to figure this out as well, so this is an extended comment rather than an answer.
When I use an app like postman (rested) it works I get 200 response, but from arduino uno wifi rev2 it fails with HTTP/1.1 422 Unprocessable Entity
{"reason":"Unprocessable Entity","error":true}
my json is just String body = "{\n \"value\": 500\n}";
and inside if (wifi.connect(serverAddress, port)) {
I call
void postData(String body) {
// send HTTP request header
wifi.println("POST /gardener/Douglas/George/water HTTP/1.1");
wifi.println("Host: " + String(serverAddress));
wifi.println("Content-Type: application/json");
wifi.println("Accept: */*");
wifi.println("Cache-Control: no-cache");
wifi.println("Accept-Encoding: gzip, deflate");
wifi.println("Accept-Language: en-us");
wifi.println("Content-Length: " + body.length()); //ANSWER: body.length() needs to be wrapped as a string, see above
wifi.println("Connection: close");
wifi.println(); // end HTTP request header
wifi.println(body);
}
fwiw a get request works fine
void getHello() {
wifi.println("GET /hello HTTP/1.1");
wifi.println("Host: " + String(serverAddress));
wifi.println("Connection: close");
wifi.println(); // end HTTP request header
}

How to send HTTP SOAP request to local Sonos device with NodeMCU?

How do I send a simple HTTP POST/GET SOAP request to my Sonos loudspeaker in Lua?
I have tried simple HTTP POST and GET requests with success, but I do not know where to start with SOAP requests.
Note: I am a newbie at this. I have never worked with a NodeMCU before nor have I programmed in Lua. I have experience in other languages though.
I know how to do it in C#, Java and PHP.
This works in Postman:
HTTP Headers:
SOAPAction:urn:schemas-upnp-org:service:AVTransport:1#Pause
Content-Type:text/xml; charset="utf-8"
Host:192.168.0.10:1400
BODY:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body><u:Pause xmlns:u="urn:schemas-upnp-org:service:AVTransport:1"><InstanceID>0</InstanceID></u:Pause></s:Body></s:Envelope>
What I did is this and it does not work:
sendRequest("192.168.0.10")
function sendRequest(url)
print("Sending request to Sonos Playbar...")
sk = net.createConnection(net.TCP, 0)
sk:on("receive", function(sck, c) print(c) end )
sk:on("connection", function(sck, c)
print("\r\n\r\n\r\n")
-- HTTP 405: Method not allowed
-- sck:send("POST / HTTP/1.1\r\nHost: "..url..":1400\r\nConnection: keep-alive\r\nAccept: */*\r\n\r\n")
-- HTTP 500, UPnP 402: Invalid arguments
-- sck:send("POST /MediaRenderer/AVTransport/Control HTTP/1.1\r\nHost: "..url..":1400\r\nSOAPAction:urn:schemas-upnp-org:service:AVTransport:1#Pause\r\nConnection: keep-alive\r\n\r\nAccept: */*\r\n\r\n")
local content = nil;
content = "POST /MediaRenderer/AVTransport/Control\r\n"
content = content.."Host:192.168.0.10:1400\r\n"
content = content.."Content-Type:text/xml; charset=utf-8\r\n"
content = content.."SOAPAction:urn:schemas-upnp-org:service:AVTransport:1#Pause\r\n"
content = content.."\r\n"
-- SOAP Body
content = content.."<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\""
content = content.." s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
content = content.."<s:Body>"
content = content.."<u:Pause xmlns:u=\"urn:schemas-upnp-org:service:AVTransport:1\">"
content = content.."<InstanceID>0</InstanceID>"
content = content.."</u:Pause>"
content = content.."</s:Body>"
content = content.."</s:Envelope>"
-- SOAP Body End
print(content.."\r\n\r\n\r\n")
sck:send(content);
end)
sk:connect(1400, url)
end
I am getting this response of my Sonos player:
HTTP/1.1 500 Internal Server Error
CONTENT-LENGTH: 347
CONTENT-TYPE: text/xml; charset="utf-8"
EXT:
Server: Linux UPnP/1.0 Sonos/34.16-37101 (ZPS9)
Connection: close
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<s:Fault>
<faultcode>s:Client</faultcode>
<faultstring>UPnPError</faultstring>
<detail>
<UPnPError xmlns="urn:schemas-upnp-org:control-1-0">
<errorCode>401</errorCode>
</UPnPError>
</detail>
</s:Fault>
</s:Body>
</s:Envelope>
What am I doing wrong? I copied and paste the text, basically. Maybe it is the order of headers? Maybe I am declaring the headers wrong or something?
I don't have a Sonos device to play with. Thus, this ain't a confirmed answer.
The string in your content variable is not a valid HTTP request. Sonos doesn't understand it as the error code 401 means "invalid action".
You need the separate HTTP headers with \r\n. An extra \r\n needs to be placed right before the HTTP body. Therefore, I'd expect that your content should be:
"POST http://192.168.0.10:1400/MediaRenderer/AVTransport/Control\r\n
SOAPAction:urn:schemas-upnp-org:service:AVTransport:1#Pause\r\n
Content-Type:text/xml; charset=\"utf-8\"\r\n
Host:192.168.0.10:1400\r\n\r\n
<s:Envelope xmlns:s=\"http://schemas.xml......"
Finally! I have it working! Below is the code to get it working:
sendRequest("192.168.0.10")
function sendRequest(url)
print("Sending request to Sonos Playbar...")
local content = nil;
content = "";
-- SOAP Body
content = content.."<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\""
content = content.." s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
content = content.."<s:Body>"
content = content.."<u:Pause xmlns:u=\"urn:schemas-upnp-org:service:AVTransport:1\">"
content = content.."<InstanceID>0</InstanceID>"
content = content.."</u:Pause>"
content = content.."</s:Body>"
content = content.."</s:Envelope>"
-- SOAP Body End
http.post("http://"..url..":1400/MediaRenderer/AVTransport/Control",
'Content-Type: text/xml; charset="utf-8"\r\n'..
'Host:'..url..':1400\r\n'..
'SOAPAction:urn:schemas-upnp-org:service:AVTransport:1#Pause\r\n',
content, function(code, data)
if(code < 0) then
print("HTTP request failed with code "..code)
else
print(code, data)
end
end)
end

How to build HTTP Response with ESP8266 and Arduino

I want to toggle some LEDs with my Android device. These LEDs are connected to the digital Pins of my Arduino which is also connected to a ESP8266. Now, my ESP8266 is defined as an AccessPoint and my Tablet can send HTTP requests (e.g. http://192.168.4.1:80/?pin=11).
I found the code here http://allaboutee.com/2015/01/20/esp8266-android-application-for-arduino-pin-control/
It works fine but my question is which HTTP header fields should be used? In this code he used some (e.g. Content-Length) but there are so much more possible (Date, Server, Content-Language,...).
Are these fields optional or which of these have to be used to build the right response?
Here is the piece of code I do not understand:
void sendHTTPResponse(int connectionId, String content)
{
String httpResponse;
String httpHeader;
httpHeader = "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=UTF-8\r\n";
httpHeader += "Content-Length: ";
httpHeader += content.length();
httpHeader += "\r\n";
httpHeader +="Connection: close\r\n\r\n";
httpResponse = httpHeader + content + " ";
sendCIPData(connectionId,httpResponse);
}
It largely depends on the client (i.e. consumer) which fields are required and which are mandatory.
The only one that is always required is "HTTP/1.1 200 OK". Of course you need to replace that status code if you're not sending an OK message.

SOAP UI - Save HTTP request to a GZIP file

I m using Soap UI free version for some rest mocking.
I need to persist my HTTP POST request (request received already compressed gzip) to a gzip file.
I have tried different ways to do that, however after to execute the code, when I try to decompress manually the file I have the following error: "The archive is either in unknown format or damaged".
The HTTP POST request has the following header:
Host : 127.0.0.1:8091
Content-Length : 636
User-Agent : Java/1.7.0_07
Connection : keep-alive
Content-Type : application/octet-stream
Accept : text/plain, application/json, application/*+json, */*
Pragma : no-cache
Cache-Control : no-cache
Below the solutions that I have tried:
Solution#1:
byte[] buffer = new byte[1024];
byte[] data = mockRequest.getRequestContent().getBytes();
def path="myfile.gz";
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(path));
bos.write(data);
bos.flush();
bos.close();
Solution#2
byte[] buffer = new byte[1024];
byte[] data = mockRequest.getRawRequestData();
def path="myfile.gz";
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(path));
bos.write(data);
bos.flush();
bos.close();
Can someone please help and let me know why I cannot decompress the gzip file and how I can do that?
Thanks,
This is Groovy, so you don't need all this Java clutter.
Here's some code that might work:
new File( path) << mockRequest.rawRequestData
EDIT
Ok, based on your comments, for zip files to be copied correctly, you probably need something a little different:
import java.nio.file.*
Files.copy(new ByteArrayInputStream(mockRequest.requestContent.bytes),
Paths.get( 'destination.zip' ) )
Tested this with an actual zip file's byte[] as source and it worked. If it does not work for you, then the byte array you're getting from requestContent.bytes just isn't a zip file.

WebRequest problem, response before request-body

I'am using the WebRequest class in .net and POST data to a server which is responding with a Response.
The wierd thing is that its working when I started fiddler to analyze my network traffic, but without fiddler it isn't.
So i started to analyze the package which is sent to and from my computer with WireShark. With in this program its simple to follow the TCP-stream. So when I had fiddler on, I can see the correct Request-header/body is sent, and gets the Response-header/body. The strange part is when i dont use fiddler the Request-header is sent, then i´ve got the Response-header/body, and finally the request-body in the end of the TCP-stream.
Here is my code i've been elaborating:
string lcUrl = "http://XX.XX.XXX.XX";
// *** Establish the request
HttpWebRequest loHttp = (HttpWebRequest)WebRequest.Create(lcUrl);
string lcPostData = testdata;
loHttp.Method = "POST";
byte [] lbPostBuffer = System.Text.Encoding.GetEncoding(1252).GetBytes(lcPostData);
loHttp.ContentLength = lbPostBuffer.Length;
loHttp.Credentials = CredentialCache.DefaultCredentials;
//loHttp.SendChunked = true;
loHttp.ServicePoint.Expect100Continue = false;
Stream loPostData = loHttp.GetRequestStream();
loPostData.Write(lbPostBuffer, 0, lbPostBuffer.Length);
loPostData.Close();
HttpWebResponse loWebResponse = (HttpWebResponse)loHttp.GetResponse();
Encoding enc = System.Text.Encoding.GetEncoding(1252);
StreamReader loResponseStream = new StreamReader(loWebResponse.GetResponseStream(), enc);
string lcHtml = loResponseStream.ReadToEnd();
loWebResponse.Close();
loResponseStream.Close();
Please use following code. Seems that you have problems with time when underlying stream are send to remote server.
string lcUrl = "http://XX.XX.XXX.XX";
// *** Establish the request
HttpWebRequest loHttp = (HttpWebRequest)WebRequest.Create(lcUrl);
string lcPostData = testdata;
loHttp.Method = "POST";
byte[] lbPostBuffer = System.Text.Encoding.GetEncoding(1252).GetBytes(lcPostData);
loHttp.ContentLength = lbPostBuffer.Length;
loHttp.Credentials = CredentialCache.DefaultCredentials;
//loHttp.SendChunked = true;
loHttp.ServicePoint.Expect100Continue = false;
using (Stream loPostData = loHttp.GetRequestStream())
{
loPostData.Write(lbPostBuffer, 0, lbPostBuffer.Length);
}
string lcHtml;
using (HttpWebResponse loWebResponse = (HttpWebResponse)loHttp.GetResponse())
{
Encoding enc = System.Text.Encoding.GetEncoding(1252);
using (StreamReader loResponseStream = new StreamReader(loWebResponse.GetResponseStream(), enc))
{
lcHtml = loResponseStream.ReadToEnd();
}
}
// Perform processing of data here....
Also I could suggest you add following code in the app.config file for your application. This is helps when server returns response that not conforms with way how .NET handle HTTP request.
<configuration>
<system.net>
<settings>
<httpWebRequest
useUnsafeHeaderParsing="true"
/>
</settings>
</system.net>
</configuration>
I have a suspicion that the client is waiting for the "HTTP/1.1 100 continue" response from the server. This is how it works. When you are posting data to the server, sometimes the server might not be ready to accept the data just yet. For eg, it wants to authenticate the client first.
So, when you send a POST request, the client just sends the request headers, with an "Expect: 100-continue" appended.
POST /url HTTP/1.1
Server: Server-name/fqdn
Content-Length: 100
Expect: 100-continue
If the server is ready to receive the data it responds with:
HTTP/1.1 100 continue
Server: server-name/fqdn
Now, the client can send the data.
However if the server is not ready to receive the data, and wants to authenticate the client, it will respond with a different status code.
If you post your wireshark trace to pastebin.com I can verify, but I suspect this is what is happening.
The reason you dont see this in fiddler might be that fiddler is using HttpListener to listen to HTTP request, and HTTP listener hides the intermediate response like 100-continue from the app (in this case fiddler).

Resources