I'm working on an ESP-01 (ESP8266 board) project and making some HTTP requests. I'm trying to refactor my code to keep it DRY, but I'm stuck on how to extract the http client initialization to a function.
Here is my working PUT request function:
void httpPut(const char* url, const char* data) {
WiFiClient client;
HTTPClient http;
http.begin(client, url);
http.addHeader("Content-Type", "application/json");
http.PUT(data);
http.end();
}
My idea was to make a function like this:
HTTPClient prepareRequest(const char* url) {
WiFiClient client;
HTTPClient http;
http.begin(client, url);
http.addHeader("Content-Type", "application/json");
return http;
}
And use it in all my request functions like this:
void httpGet(const char* url) {
HTTPClient http = prepareRequest(url);
http.GET();
http.end();
}
I get this compiler error, but I'm not really sure what it means (row 90 mentioned is return http;):
/home/<user>/Code/Micro/mittari-micro/src/mittari.ino: In function 'HTTPClient prepareRequest(const char*)':
/home/<user>/Code/Micro/mittari-micro/src/mittari.ino:90:10: error: use of deleted function 'HTTPClient::HTTPClient(const HTTPClient&)'
90 | return http;
| ^~~~
In file included from /home/<user>/Code/Micro/mittari-micro/src/mittari.ino:2:
/home/<user>/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.h:151:7: note: 'HTTPClient::HTTPClient(const HTTPClient&)' is implicitly deleted because the default definition would be ill-formed:
151 | class HTTPClient
| ^~~~~~~~~~
/home/<user>/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.h:151:7: error: use of deleted function 'std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = StreamString; _Dp = std::default_delete<StreamString>]'
In file included from /home/<user>/.platformio/packages/toolchain-xtensa/xtensa-lx106-elf/include/c++/10.3.0/memory:83,
from /home/<user>/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src/ESP8266WiFiGeneric.h:28,
from /home/<user>/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src/ESP8266WiFiSTA.h:28,
from /home/<user>/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src/ESP8266WiFi.h:34,
from /home/<user>/Code/Micro/mittari-micro/src/mittari.ino:1:
/home/<user>/.platformio/packages/toolchain-xtensa/xtensa-lx106-elf/include/c++/10.3.0/bits/unique_ptr.h:468:7: note: declared here
468 | unique_ptr(const unique_ptr&) = delete;
| ^~~~~~~~~~
Is it possible to extract the common functionality to a function and how could I do that?
You are trying to return a HTTPClient object (http) by value at the end of your prepareRequest function. This would mean copy construction in this case and the compiler is telling you that copy construction is deliberately disabled for this class. (There can be quite a number of reasons for this.)
How it is customary to do this in an Arduino environment is to move your "WiFiClient client" and "HTTPClient http" to global scope. Then write your initialization code into the "setup" function and finally you can use them in whatever functions you like. That way you can separate the setting of the URL and header into the "prepareRequest" function the actual http get into the "httpGet" function without them returning anything.
There are more complicated solutions with const references, pointers, etc... But unless you specifically want to go down that road, I would go for the solution mentioned above.
I put the WiFiClient and HTTPClient in global scope and changed the preparation method to void like this:
WiFiClient client;
HTTPClient http;
void prepareRequest(const char* url) {
http.end(); // In case previous request did not call end for some reason.
http.begin(client, url);
http.addHeader("Content-Type", "application/json");
}
void httpGet(const char* url) {
prepareRequest(url);
http.GET();
http.end();
}
It seems to work fine so far.
Related
I have written a HTTP client, where I am reading the data response from a REST web service. My confusion arises after reading multiple blogs on EntityUtils.consume() and EntiryUtils.toString(). I wanted to know the following:
If EntityUtils.toString(..) ONLY is sufficient as it also closes the stream after reading char bytes. Or I should also do EntityUtils.consume(..) as a good practice.
If both toString() and consume() operation can be used. If yes, then what should be there order.
If I EntityUtils.toString() closes the stream; then why the next call in EntityUtils.consume(..) operations which is entity.isStreaming() still returns true?
Could anyone guide me here to use these operations in a standard way. I am using HTTP version 4+.
I have to use these configurations in multithreaded(web-app) environment.
Thanks
I looked at the recommended example from the apache httpclient commons website.
In the example, they used EntityUtils.toString(..) without needing to use EntityUtils.consume(..) before or after.
They mention that calling httpclient.close() ensures all resources are closed.
source: https://hc.apache.org/httpcomponents-client-ga/httpclient/examples/org/apache/http/examples/client/ClientWithResponseHandler.java
CloseableHttpClient httpclient = HttpClients.createDefault();
try {
HttpGet httpget = new HttpGet("http://httpbin.org/");
System.out.println("Executing request " + httpget.getRequestLine());
// Create a custom response handler
ResponseHandler<String> responseHandler = new ResponseHandler<String>() {
#Override
public String handleResponse(
final HttpResponse response) throws ClientProtocolException, IOException {
int status = response.getStatusLine().getStatusCode();
if (status >= 200 && status < 300) {
HttpEntity entity = response.getEntity();
return entity != null ? EntityUtils.toString(entity) : null;
} else {
throw new ClientProtocolException("Unexpected response status: " + status);
}
}
};
String responseBody = httpclient.execute(httpget, responseHandler);
System.out.println("----------------------------------------");
System.out.println(responseBody);
} finally {
httpclient.close();
}
This is what is quoted for the above example:
This example demonstrates how to process HTTP responses using a response handler. This is the recommended way of executing HTTP requests and processing HTTP responses. This approach enables the caller to concentrate on the process of digesting HTTP responses and to delegate the task of system resource deallocation to HttpClient. The use of an HTTP response handler guarantees that the underlying HTTP connection will be released back to the connection manager automatically in all cases.
I'm programming my ESP32 with the ArduinoIDE and have a problem with HTTP GET. What I'm doing:
the ESP32 connects as WiFi client to an existing WiFi network using a static, fixed IP
a webserver is started which provides a webpage for OTA firmware update -> this works, the webpage is accessible via the static IP
using HttpClient I try to GET an other, remote webserver, but this fails
This is the code I'm using for the HTTP GET call:
static WiFiClient wifi;
HttpClient wlanHttp=HttpClient(wifi,"my.server.tld");
wlanHttp.get("/setpos.php?id=DEADBEEF"); // -> this fails with error code -1
wlanHttp.responseStatusCode(); // follow-up error -1
wlanHttp.stop();
Any idea what goes wrong here?
The confusing part here is the ESP32 has a built in http client called HTTPClient. The one for Arduino is called HttpClient and I'd like to find the guy who decided on that name and see if he's okay. HTTPClient has a routine called getString() that is a lovely way to gather info from a json api call, but HttpClient won't compile with that because it has no clue what that is.
On ESp32 (if using the HTTPClient.h) the code should look like that:
static WiFiClient wifi;
HttpClient wlanHttp;
wlanHttp.begin("http://my.server.tld/setpos.php?id=DEADBEEF"); //Specify the URL
int httpCode = wlanHttp.GET(); //Make the request
if (httpCode > 0) { //Check for the returning code
if (httpCode == HTTP_CODE_OK) {
// get payload with http.getString();
Serial.println(httpCode);
// Serial.println(payload);
} else {
Serial.printf("[HTTP] GET... failed, error: %s\n", wlanHttp.errorToString(httpCode).c_str());
}
} else {
Serial.println("Error on HTTP request");
}
wlanHttp.end(); //Free the resources
I'm having troubles setting the no_delay option on an asio socket. The following code runs well, except for the delay. My server receives the messages only after the 5000 ms expire.
#include <boost/asio.hpp>
#include <boost/thread.hpp>
using namespace boost::asio;
struct Client
{
io_service svc;
ip::tcp::socket sock;
Client() : svc(), sock(svc)
{
ip::tcp::resolver resolver(svc);
ip::tcp::resolver::iterator endpoint = resolver.resolve(boost::asio::ip::tcp::resolver::query("127.0.0.1", "32323"));
connect(sock, endpoint);
}
void send(std::string const& message) {
sock.send(buffer(message));
}
};
int main()
{
Client client;
client.send("hello world\n");
client.send("bye world\n");
boost::this_thread::sleep_for(boost::chrono::milliseconds(5000));
}
When trying to add a delay I have a few options:
1) Add the option before connection:
Client() : svc(), sock(svc)
{
ip::tcp::resolver resolver(svc);
ip::tcp::resolver::iterator endpoint = resolver.resolve(boost::asio::ip::tcp::resolver::query("127.0.0.1", "32323"));
sock.set_option(ip::tcp::no_delay(true));
connect(sock, endpoint);
}
However this throws set_option: Bad file descriptor
2) Add the option after the connection:
Client() : svc(), sock(svc)
{
ip::tcp::resolver resolver(svc);
ip::tcp::resolver::iterator endpoint = resolver.resolve(boost::asio::ip::tcp::resolver::query("127.0.0.1", "32323"));
connect(sock, endpoint);
sock.set_option(ip::tcp::no_delay(true));
}
However in this case, the option has no effect and I still see the delay. According to boost::asio with no_delay not possible? , I need to set the option after I've opened the socket but before I've connected the socket. So I've tried this:
Client() : svc(), sock(svc)
{
ip::tcp::endpoint endpoint( ip::address::from_string("127.0.0.1"), 32323);
sock.open(ip::tcp::v4());
sock.set_option(ip::tcp::no_delay(true));
sock.connect(endpoint);
}
However, I still see no effect. How can I set this option?
Edit: It's possible that I am not setting the option correctly on the server-side. This is the complete server code:
#include <boost/asio.hpp>
#include <iostream>
int main() {
boost::asio::io_service io_service;
boost::asio::ip::tcp::acceptor acceptor(io_service, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), 32323));
boost::asio::ip::tcp::socket socket(io_service);
acceptor.accept(socket);
socket.set_option(boost::asio::ip::tcp::no_delay(true));
boost::asio::streambuf sb;
boost::system::error_code ec;
while (boost::asio::read(socket, sb, ec)) {
std::cout << "received:\n" << &sb;
}
}
The client is properly setting the ip::tcp::no_delay option. However, the delay being observed is not the result of this option. Instead, it is the result of the server attempting to read more data than the client has sent, and when the client exits after sleeping 5000ms, the server's read operation completes with an error.
The read() operation initiated by the server will complete when either it has read streambuf.max_size() bytes or an error occurs. The streambuf's max size defaults to std::numeric_limits<std::size_t>::max() and can be configured in its constructor. In this case, the server attempts to read std::numeric_limits<std::size_t>::max() bytes, but the client only sends 22 bytes, sleeps 5000ms, then closes the socket. When the server observes that connection has closed, the read() operation completes with 22 bytes read and an error code of boost::asio::error::eof.
I have the following dart program:
import "dart:io";
import "dart:convert" show UTF8;
void main() {
HttpClient client = new HttpClient();
client.badCertificateCallback = (certificate, host, callbackPort) {
print("In bad certificate callback.");
return true;
};
client.getUrl(Uri.parse("https://www.self.signed.url.com/api")).then((HttpClientRequest request) {
print("In request callback.");
return request.close();
}).then((HttpClientResponse resp) {
print("In responce callback.");
});
}
This code makes a get request to a url. The url uses a self signed certificate, which results in an SSL error. To get around this I have set the badCertificateCallback of HttpClient to always return true, effectively accepting all certificates.
With this code I would expect to see following output:
In request callback.
In bad certificate callback.
In responce callback.
And then have the program exit. Instead I see:
In bad certificate callback.
In request callback.
And the program hangs. Any ideas on what I am doing wrong?
UPDATE: I have submitted a Dart bug as advised by some of the comments. It can be found here. If anything comes of that I'll put the results back into this.
It turns out that it has nothing with HTTPS to do, but with the handling of the HTTP protocol on the https://checkmate.fogbugz.com/api.xml server. The Dart HTTP stack send all headers field names in lower case. This is not handled correctly by the server.
The following code illustrates this:
import 'dart:io';
import 'dart:convert';
void main() {
SecureSocket.connect("checkmate.fogbugz.com", 443).then((socket) {
socket.write("GET /api.xml HTTP/1.0\r\n"
"host: checkmate.fogbugz.com\r\n"
"\r\n");
socket.listen((data) => print(UTF8.decode(data)));
});
}
No response is ever received. If host: is changed to Host: the response is received.
RFC 2616 section 4.2 states: 'Field names are case-insensitive.'
I started off by trying to use HTTPRequest in dart:html but quickly realised that this is not possible in a console application. I have done some Google searching but can't find what I am after (only finding HTTP Server), is there a method of sending a normal HTTP request via a console application?
Or would I have to go the method of using the sockets and implement my own HTTP request?
There's an HttpClient class in the IO library for making HTTP requests:
import 'dart:io';
void main() {
HttpClient client = new HttpClient();
client.getUrl(Uri.parse("http://www.dartlang.org/"))
.then((HttpClientRequest request) {
return request.close();
})
.then(HttpBodyHandler.processResponse)
.then((HttpClientResponseBody body) {
print(body.body);
});
}
Update: Since HttpClient is fairly low-level and a bit clunky for something simple like this, the core Dart team has also made a pub package, http, which simplifies things:
import 'package:http/http.dart' as http;
void main() {
http.get('http://pub.dartlang.org/').then((response) {
print(response.body);
});
}
I found that the crypto package was a dependency, so my pubspec.yaml looks like this:
name: app-name
dependencies:
http: any
crypto: any
You'll be looking for the HttpClient which is part of the server side dart:io SDK library.
Example taken from the API doc linked to above:
HttpClient client = new HttpClient();
client.getUrl(Uri.parse("http://www.example.com/"))
.then((HttpClientRequest request) {
// Prepare the request then call close on it to send it.
return request.close();
})
.then((HttpClientResponse response) {
// Process the response.
});