ESP8266 Skips some data from GET request over HTTPS - https

Starting from the WiFiClient HTTPS Request example, I'm trying to get some code from a Github Gist using their API, but some parts of the file are missing
[...]
const char* host = "api.github.com";
const int httpsPort = 443;
String url = "/gists/0b3d5d67342c055cc9ba34b59d075da7";
// Use web browser to view and copy
// SHA1 fingerprint of the certificate
const char fingerprint[] PROGMEM = "59 74 61 88 13 CA 12 34 15 4D 11 0A C1 7F E6 67 07 69 42 F5";
void setup() {
Serial.begin(115200);
wifiSetup();
// Use WiFiClientSecure class to create TLS connection
WiFiClientSecure client;
Serial.print("Connecting to ");
Serial.println(host);
Serial.printf("Using fingerprint '%s'\n", fingerprint);
client.setFingerprint(fingerprint);
if (!client.connect(host, httpsPort)) {
Serial.println("Connection failed");
return;
}
Serial.print("Requesting URL: ");
Serial.println(url);
client.print(String("GET ") + url + " HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"User-Agent: ESP8266\r\n" +
"Connection: close\r\n\r\n");
Serial.println("Request sent");
while (client.connected()) {
String line = client.readStringUntil('\n');
if (line == "\r") {
Serial.println("Headers received");
break;
}
}
String line = client.readStringUntil('\n');
line = line.substring(line.indexOf("content\":") + 10, line.indexOf("},") - 2);
Serial.println("Reply was:");
Serial.println("==========");
Serial.println(toHTML(line));
Serial.println("==========");
Serial.println("Setup end");
}
void loop() {
}
String toHTML(String source) {
Serial.print("toHTML length before/after: ");
Serial.print(source.length());
Serial.print("/");
source.replace("\\\\n", "==NEWLINE==");//Preserve document newlines
source.replace("\\n", "\n");//Replace newline
source.replace("==NEWLINE==", "\\n");
source.replace("\\\"", "\"");//Replace quotes
source.replace("\\t", "\t");//Replace tabs
Serial.println(source.length());
return source;
}
File I'm trying to get:
[...]
function printDebugMap() {
map.forEach((value, key) => {
printDebug(`${key} = [${value}]`);
});
}
</script>
</head>
<body>
<h1 id="AppName">Spreadsheet Automatico</h1>
<form onsubmit="return doNameInput()">
<p>Nome giocatori:</p>
<input type="text" id="nameBox">
<input type="submit" value="Submit">
</form>
<hr/>
What I actually get: some text is skipped
[...]
function printDebugMap() {
map.forEach((value, key) => {
printDebug(`${ype="text" id="nameBox">
<input type="submit" value="Submit">
</form>
<hr/>
when I add some random unrelated code which part is missing part changes, so I don't know what I'm doing wrong

Related

ESP 8266 Multiple Get calls on same Client?

I'm trying to make Multiple GET calls using the same client. I've tried multiple things. New Variables, New instances. Closing Clients. The second Stock Call doesnt go through ever. It fails at "while (client.connected())" on the second Call. It Doesnt get to returning headers.
Any Ideas?
Code look like this
void loop()
{
WiFiClientSecure client;
//Stock
String payload;
// Use WiFiClientSecure class to create TLS connection
Serial.print("connecting to ");
Serial.println(host);
Serial.printf("Using fingerprint '%s'\n", fingerprint);
client.setFingerprint(fingerprint);
if (!client.connect(host, httpsPort))
{
Serial.println("connection failed");
client.stop();
//return;
}
//AAPL
String url = "/api/v1/quote?symbol=AAPL&token=XXXX";
Serial.print("requesting URL: ");
Serial.println(url);
client.print(String("GET ") + url + " HTTP/1.0\r\n" +
"Host: " + host + "\r\n" +
"User-Agent: BuildFailureDetectorESP8266\r\n" +
"Connection: close\r\n\r\n");
Serial.println("request sent");
while (client.connected())
{
String line = client.readStringUntil('\n');
// String line = client.readStringUntil('\r');
Serial.println(line);
if (line == "\r")
{
Serial.println("headers received");
payload = client.readStringUntil('\r');
//break;
}
}
Serial.println("payload");
Serial.println(payload);
// String line = client.readString();
//JsonObject& obj = doc.parseObject(payload);
StaticJsonDocument<200> doc;
DeserializationError error = deserializeJson(doc, payload);
// Test if parsing succeeds.
if (error) {
Serial.print(F("deserializeJson() failed: "));
Serial.println(error.f_str());
// return;
client.stop();
}
//{"c":23.35,"d":-0.73,"dp":-3.0316,"h":24.6986,"l":23.25,"o":24.54,"pc":24.08,"t":1627588802}
double currentprice = doc["c"];
double pctchg = doc["d"];
// Print values.
Serial.println(currentprice);
Serial.println(pctchg);
Serial.println("closing connection");
client.stop();
delay(20000);
//SPY
String spyurl = "/api/v1/quote?symbol=SPY&token=XXXX";
Serial.print("requesting URL: ");
Serial.println(spyurl);
client.print(String("GET ") + spyurl + " HTTP/1.0\r\n" +
"Host: " + host + "\r\n" +
"User-Agent: BuildFailureDetectorESP8266\r\n" +
"Connection: close\r\n\r\n");
Serial.println("request sent");
String spypayload;
while (client.connected())
{
String spyline = client.readStringUntil('\n');
// String line = client.readStringUntil('\r');
Serial.println(spyline);
if (spyline == "\r")
{
Serial.println("headers received");
spypayload = client.readStringUntil('\r');
//break;
}
}
Serial.println("spypayload");
Serial.println(spypayload);
// String line = client.readString();
//JsonObject& obj = doc.parseObject(payload);
StaticJsonDocument<200> spydoc;
DeserializationError spyerror = deserializeJson(spydoc, spypayload);
// Test if parsing succeeds.
if (spyerror) {
Serial.print(F("deserializeJson() failed: "));
Serial.println(spyerror.f_str());
// return;
client.stop();
}
//{"c":23.35,"d":-0.73,"dp":-3.0316,"h":24.6986,"l":23.25,"o":24.54,"pc":24.08,"t":1627588802}
double SPYcurrentprice = spydoc["c"];
double SPYpctchg = spydoc["d"];
// Print values.
Serial.println(SPYcurrentprice);
Serial.println(SPYpctchg);
Serial.println("closing connection");
client.stop();
I'm an idiot.
Needed to keep the connection open on the first call
"Connection: Keep-Alive\r\n\r\n");

ESP32 OTA via ETH

I have an running and working system which is an ESP32 with an LAN8720 to communicate over the internet.
Now just plane HTTP requests (via the WiFiClientSecure client) work like a charm. But I also need (over https) to update the device.
Now I currently have this code:
#include <Arduino.h>
#include <Update.h>
#include <HTTPUpdate.h>
WiFiClientSecure otaClient;
Serial.println("Preparing to update");
// Do OTA update
otaClient.setInsecure(); //skip verification of SSL cert
if (!otaClient.connect(DIGITAL_HQ_BASE_URL, 443)) {
Serial.println("Could not connect.");
}
otaClient.print("GET "); // watch the space!
otaClient.print(DIGITAL_HQ_BINARY_ENDPOINT); // API endpoint
otaClient.println(" HTTP/1.1"); // watch the space!
otaClient.print("Host: ");
otaClient.println(DIGITAL_HQ_BASE_URL);
otaClient.println("Connection: keep-alive"); // Don't close, since we need to perform OTA
otaClient.print("User-Agent: ");
otaClient.println(DIGITAL_HQ_USER_AGENT);
otaClient.println("Cache-Control: no-cache");
otaClient.println();
unsigned long timeout = millis();
while (otaClient.available() == 0) {
if (millis() - timeout > 5000) {
Serial.println("Client Timeout !");
otaClient.stop();
return;
}
}
while (otaClient.available()) {
// read line till /n
String line = otaClient.readStringUntil('\n');
// remove space, to check if the line is end of headers
line.trim();
// if the the line is empty,
// this is end of headers
// break the while and feed the
// remaining `client` to the
// Update.writeStream();
if (!line.length()) {
//headers ended
break; // and get the OTA started
}
// Check if the HTTP Response is 200
// else break and Exit Update
if (line.startsWith("HTTP/1.1")) {
if (line.indexOf("200") < 0) {
Serial.println("Got a non 200 status code from server. Exiting OTA Update.");
break;
}
}
// extract headers here
// Start with content length
if (line.startsWith("Content-Length: ")) {
contentLength = atol((getHeaderValue(line, "Content-Length: ")).c_str());
Serial.println("Got " + String(contentLength) + " bytes from server");
}
// Next, the content type
if (line.startsWith("Content-Type: ")) {
String contentType = getHeaderValue(line, "Content-Type: ");
Serial.println("Got " + contentType + " payload.");
if (contentType == "application/octet-stream") {
isValidContentType = true;
}
}
}
Serial.println("contentLength : " + String(contentLength) + ", isValidContentType : " + String(isValidContentType));
if (contentLength && isValidContentType) {
// Check if there is enough to OTA Update
bool canBegin = Update.begin(contentLength);
// If yes, begin
if (canBegin) {
Serial.println("Begin OTA. This may take 2 - 5 mins to complete. Things might be quite for a while.. Patience!");
// No activity would appear on the Serial monitor
// So be patient. This may take 2 - 5mins to complete
size_t written = Update.writeStream(otaClient);
if (written == contentLength) {
Serial.println("Written : " + String(written) + " successfully");
} else {
Serial.println("Written only : " + ConvertFormatBytes(written) + "/" + ConvertFormatBytes(contentLength));
// retry??
// execOTA();
}
if (Update.end()) {
Serial.println("OTA done!");
if (Update.isFinished()) {
Serial.println("Update successfully completed. Rebooting.");
ESP.restart();
} else {
Serial.println("Update not finished? Something went wrong!");
}
} else {
Serial.println("Error Occurred. Error #: " + String(Update.getError()));
}
} else {
// not enough space to begin OTA
// Understand the partitions and
// space availability
Serial.println("Not enough space to begin OTA");
otaClient.stop();
}
} else {
Serial.println("There was no content in the response");
otaClient.stop();
}
This runs without errors, but is frozen on the Preparing to update console message. Anyone who has an idea what I'm doing wrong here?
The file NEEDS to come from an https domain.

Unable to connect https protocol with ESP8266 using WifiClientSecure

I'm trying to get ESP8266 position by unwiredlabs. I followed with this introduction.
This is my arduino code:
#include <ESP8266HTTPClient.h>
#include <ArduinoJson.h>
#include "ESP8266WiFi.h"
char myssid[] = "Your wifi/hotspot name";
char mypass[] = "Your password";
const char* Host = "www.unwiredlabs.com";
String endpoint = "/v2/process.php";
String token = "d99cccda52ec0b";
String jsonString = "{\n";
double latitude = 0.0;
double longitude = 0.0;
double accuracy = 0.0;
void setup(){
Serial.begin(115200);
// Set WiFi to station mode and disconnect from an AP if it was previously connected
WiFi.mode(WIFI_STA);
WiFi.disconnect();
Serial.println("Setup done");
// We start by connecting to a WiFi network
Serial.print("Connecting to ");
Serial.println(myssid);
WiFi.begin(myssid, mypass);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println(".");
}
void loop() {
char bssid[6];
DynamicJsonBuffer jsonBuffer;
// WiFi.scanNetworks will return the number of networks found
int n = WiFi.scanNetworks();
Serial.println("scan done");
if (n == 0 ) {
Serial.println("No networks available");
} else {
Serial.print(n);
Serial.println(" networks found");
}
// now build the jsonString...
jsonString = "{\n";
jsonString += "\"token\" : \"";
jsonString += token;
jsonString += "\",\n";
jsonString += "\"id\" : \"saikirandevice01\",\n";
jsonString += "\"wifi\": [\n";
for (int j = 0; j < n; ++j) {
jsonString += "{\n";
jsonString += "\"bssid\" : \"";
jsonString += (WiFi.BSSIDstr(j));
jsonString += "\",\n";
jsonString += "\"signal\": ";
jsonString += WiFi.RSSI(j);
jsonString += "\n";
if (j < n - 1) {
jsonString += "},\n";
} else {
jsonString += "}\n";
}
}
jsonString += ("]\n");
jsonString += ("}\n");
Serial.println(jsonString);
WiFiClientSecure client;
//Connect to the client and make the api call
Serial.println("Requesting URL: https://" + (String)Host + endpoint);
if (client.connect(Host, 443)) {
Serial.println("Connected");
client.println("POST " + endpoint + " HTTP/1.1");
client.println("Host: " + (String)Host);
client.println("Connection: close");
client.println("Content-Type: application/json");
client.println("User-Agent: Arduino/1.0");
client.print("Content-Length: ");
client.println(jsonString.length());
client.println();
client.print(jsonString);
delay(500);
}
//Read and parse all the lines of the reply from server
while (client.available()) {
String line = client.readStringUntil('\r');
JsonObject& root = jsonBuffer.parseObject(line);
if (root.success()) {
latitude = root["lat"];
longitude = root["lon"];
accuracy = root["accuracy"];
Serial.println();
Serial.print("Latitude = ");
Serial.println(latitude, 6);
Serial.print("Longitude = ");
Serial.println(longitude, 6);
Serial.print("Accuracy = ");
Serial.println(accuracy);
}
}
Serial.println("closing connection");
Serial.println();
client.stop();
delay(5000);
}
When code had been flashed to esp8266, it showed that could not to connect to https://www.instructables.com/v2/process.php.
ESP serial output:
... // some initial setup string
Requesting URL: https://unwiredlabs.com/v2/process.php
// if connected, "connected" was printed here, but not
closing connection
Then, I tried to use url https://unwiredlabs.com/v2/process.php on chrome browser. This is message:
{
status: "error",
message: "Invalid request",
balance: 0,
help: "Check for misplaced commas and use double quotes to encapsulate strings"
}
This proved that this url existed, but when i tried on Postman, it showed:
Then, I turned off SSL certificate verifycation on Postman. It responsed with a 403 Forbidden error.
So i think the reason caused problem is SSL certificate verifycation of WifiClientSecure.
Anyone can help?
SSL - at least the way this code is trying to use it - requires the Fingerprint of the site you're trying to connect to. The code needs to tell its client object the fingerprint of the site before trying to connect to that site.
Step 1: manually retrieve the fingerprint from the site:
I browsed to https://www.unwiredlabs.com in Chrome and copied the site cerficate, then used openSSL in git bash on Windows to extract the fingerprint:
openssl x509 -noout -fingerprint -sha1 -inform pem -in certificate-file.cer > fingerprint.txt
I then edited the resulting fingerprint.txt file, replacing each ':' with a space.
Search the net for details on how to copy the cite certificate using Chrome, or whatever browser you're using.
Step 2: edit the code to add the fingerprint to the Sketch:
I added the constant 'sslFingerprint', and added a call to client.setFingerprint() just before calling client.connect().
I then removed the code unrelated to connecting to the site, creating an example Sketch that illustrates a successful connection to unwiredlabs.com:
#include <ESP8266HTTPClient.h>
#include "ESP8266WiFi.h"
// The SSL Fingerprint of https://www.unwiredlabs.com
// Certificate expires ‎October ‎9, ‎2020
const char *sslFingerprint
= "C3 3E 2F 21 CB 15 4E 02 D5 27 E5 F6 EF FB 31 AE 91 51 A3 5D";
char myssid[] = "yourWiFiSSID";
char mypass[] = "yourWiFiPassword";
const char* Host = "www.unwiredlabs.com";
String endpoint = "/v2/process.php";
void setup() {
Serial.begin(9600);
// Set WiFi to station mode and disconnect from an AP if it was previously connected
WiFi.mode(WIFI_STA);
WiFi.disconnect();
Serial.println("Setup done");
// We start by connecting to a WiFi network
Serial.print("Connecting to ");
Serial.println(myssid);
WiFi.begin(myssid, mypass);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println(".");
}
void loop() {
WiFiClientSecure client;
//Connect to the client and make the api call
Serial.println("Requesting URL: https://" + (String)Host + endpoint);
client.setFingerprint(sslFingerprint);
if (client.connect(Host, 443)) {
Serial.println("Connected");
}
Serial.println("closing connection");
Serial.println();
client.stop();
delay(5000);
}
When run on a Sparkfun ESP8266 Thing Dev board, the Sketch produces this output:
......
Requesting URL: https://www.unwiredlabs.com/v2/process.php
Connected
closing connection

Issue with file upload in Web API 2 other then html form post

Issue with file upload in Web API 2
While i upload file with form post this work file but while i am sending same information from Rest Client like Postman in Google Chrome or posting file information with Android or iOS application Web Api 2 code is not detecting file multipart/form-post
Following is my html code
<form enctype="multipart/form-data" method="post" action="/api/feedback">
<input type="text" name="Scope" placeholder="Scope" value="feedback">
<input type="text" name="Lang" placeholder="Lang" value="en">
<input type="text" name="Collection" placeholder="Collection" value="7b22437573746f6d65724964223a392c2253657276696365526571756573744964223a312c224174746974756465223a332e322c22576f726b457468696373223a342e322c2248796769656e65223a352c22416d6f757450616964223a333132332e327d">
<input type="file" name="Photo1" placeholder="Photo1">
<input type="file" name="Photo2" placeholder="Photo2">
<input type="file" name="Signature" placeholder="Signature">
<input type="submit" value="Submit">
</form>
This .Net Web Api 2 Code
public async Task<Result> Post()
{
var model = new ApiData();
var result = new Result();
if (Request.Content.IsMimeMultipartContent())
{
try
{
string root = HttpContext.Current.Server.MapPath("~/assets/temp");
var provider = new MultipartFormDataStreamProvider(root);
var sb = new System.Text.StringBuilder(); // Holds the response body
// Read the form data and return an async task.
await Request.Content.ReadAsMultipartAsync(provider);
// This illustrates how to get the form data.
foreach (var key in provider.FormData.AllKeys)
{
foreach (var val in provider.FormData.GetValues(key))
{
if (key.Equals("Scope"))
model.Scope = val;
if (key.Equals("Lang"))
model.Lang = val;
if (key.Equals("Collection"))
model.Collection = val;
}
}
if (!model.Scope.Equals("feedback", StringComparison.InvariantCultureIgnoreCase))
result.Text = "Missing Scope.";
else
{
var Util = new Utils();
model.Collection = Util.ToString(model.Collection);
var _Feedback = JsonConvert.DeserializeObject<Feedback>(model.Collection);
try
{
// This illustrates how to get the file names for uploaded files.
foreach (var file in provider.FileData)
{
FileInfo fileInfo = new FileInfo(file.LocalFileName);
string BaseUrl = HttpContext.Current.Server.MapPath("~/assets/feedback/");
string oldFile = file.Headers.ContentDisposition.FileName.TrimStart('"').TrimEnd('"');
string NewName = string.Format("{0}{1}", Util.NewGuid, Path.GetExtension(oldFile));
if (file.Headers.ContentDisposition.Name.ToLower().Contains("photo"))
{
_Feedback.Photos = string.IsNullOrEmpty(_Feedback.Photos) ? NewName : _Feedback.Photos + "," + NewName;
fileInfo.MoveTo(BaseUrl + NewName);
}
else if (string.IsNullOrEmpty(_Feedback.Signatures) && file.Headers.ContentDisposition.Name.ToLower().Contains("signature"))
{
_Feedback.Signatures = NewName;
fileInfo.MoveTo(BaseUrl + NewName);
}
}
}
catch { }
if (Util.IsRequired(_Feedback.CustomerId)
&& Util.IsRequired(_Feedback.ServiceRequestId)
&& Util.IsRequired(_Feedback.Attitude)
&& Util.IsRequired(_Feedback.WorkEthics)
&& Util.IsRequired(_Feedback.Hygiene)
)
{
var feedback = new Feedback()
{
CustomerId = _Feedback.CustomerId,
ServiceRequestId = _Feedback.ServiceRequestId,
Attitude = _Feedback.Attitude,
WorkEthics = _Feedback.WorkEthics,
Hygiene = _Feedback.Hygiene,
AmoutPaid = _Feedback.AmoutPaid,
Photos = _Feedback.Photos,
Signatures = _Feedback.Signatures,
Created = DateTime.UtcNow,
Updated = DateTime.UtcNow
};
db.Feedbacks.Add(feedback);
db.SaveChanges();
if (feedback.Id != default(int))
{
result.Success = true;
result.Text = "Success";
}
}
else
{
result.Text = "Required Parameters missing.";
}
}
}
catch
{
result.Text = "Error";
}
}
else
result.Text = "Signature or photo missing";
return result;
}
This is Postmen request
Exposing post request in Fiddler Client i have seen that while posting using html form post there is boundary in multipart/form-data; although there is no boundary in Postman or other RestClient.
This may be a Postman bug.
To summarize, if you set the Content-Type header with Postman, it seems to override the default browser functionality and not add the boundary. Most the time this is a good thing, but not for multipart/form-data since we need the browser to add the boundary for us. Try removing the Content-Type header. When sending the actual Post, the browser will automatically add the proper header and create the boundary.
Your WebApi code looks fine. Your if check will just be false.
//This will be false since it's a malformed request.
if (Request.Content.IsMimeMultipartContent())
I know this an old post but I think this could help someone who has the same issue. In postman remove Content-Type header. The request will go through fine.

Multiline texbox validation

I want to validate maxlegnth of 5 characters in each row of the multiline textbox
Help me
Here's an example: A TextArea and span to show the validation results.
<textarea cols="30" rows="10" onblur="validateRows(this)"></textarea><br/>
<span id="validationResults" style="color:red"></span>
Here's the JavaScript code to validate each row:
function validateRows(e){
var content = e.value.split("\n");
for(var line in content){
var charLength = content[line].length - 1;
var lineNumber = parseInt(line) + 1;
if(charLength > 5){
document.getElementById("validationResults").innerHTML += "* line "
+ lineNumber + " has " + charLength
+ " characters" + "<br/>";
}
}
}
This is a C# version. Can be used either in Web applications for server side validation or Windows applications. (In Web applications for client side validation, Jose Basilio's code is appropriate)
public static bool HasMax5CharsPerLine(TextBox target)
{
foreach (string Line in target.Text.Split(new char[] {'\n'}))
if (Line.Length > 5)
return false;
return true;
}
using split function(both in C# and Javascript) and then check length it.
var temp = [TextFromTextBox].split('\n');
foreach(var s in temp)
{
if(!ValidateFunction(s))
{
// code for show exception
}
}

Resources