88#define ASYNC_HTTP_DEBUG (...) // DEBUG_PORT.printf(__VA_ARGS__)
99#endif
1010
11- // TODO: customizable headers
12- // <method> <path> <host> <len>
13- const char HTTP_REQUEST_TEMPLATE [] PROGMEM =
14- " %s %s HTTP/1.1\r\n "
15- " Host: %s\r\n "
16- " User-Agent: ESPurna\r\n "
17- " Connection: close\r\n "
18- " Content-Type: application/x-www-form-urlencoded\r\n "
19- " Content-Length: %u\r\n "
20- " \r\n " ;
11+ namespace Headers {
12+ PROGMEM const char HOST [] = " Host" ;
13+ PROGMEM const char USER_AGENT [] = " User-Agent" ;
14+ PROGMEM const char CONNECTION [] = " Connection" ;
15+ PROGMEM const char CONTENT_TYPE [] = " Content-Type" ;
16+ PROGMEM const char CONTENT_LENGTH [] = " Content-Length" ;
17+ };
18+
19+ struct AsyncHttpHeader {
20+
21+ using header_t = std::pair<const String&, const String&>;
22+
23+ private:
24+
25+ const String _key;
26+ const String _value;
27+ header_t _kv;
28+
29+ public:
30+
31+ AsyncHttpHeader (const char * key, const char * value) :
32+ _key (FPSTR (key)),
33+ _value (FPSTR (value)),
34+ _kv (_key, _value)
35+ {}
36+
37+ AsyncHttpHeader (const String& key, const String& value) :
38+ _key (key),
39+ _value (value),
40+ _kv (_key, _value)
41+ {}
42+
43+ AsyncHttpHeader (const AsyncHttpHeader& other) :
44+ _key (other._key),
45+ _value (other._value),
46+ _kv (_key, _value)
47+ {}
48+
49+ const header_t & get () const {
50+ return _kv;
51+ }
52+
53+ const char * key () const {
54+ return _key.c_str ();
55+ }
56+
57+ const char * value () const {
58+ return _value.c_str ();
59+ }
60+
61+ size_t keyLength () const {
62+ return _key.length ();
63+ }
64+
65+ size_t valueLength () const {
66+ return _value.length ();
67+ }
68+
69+ bool operator ==(const AsyncHttpHeader& header) {
70+ return (
71+ (header._key == _key) && (header._value == _value)
72+ );
73+ }
74+
75+ };
76+
77+ struct AsyncHttpHeaders {
78+
79+ using header_t = AsyncHttpHeader;
80+ using headers_t = std::vector<header_t >;
81+
82+ private:
83+
84+ headers_t _headers;
85+ size_t _index;
86+ size_t _last;
87+ String _value;
88+
89+ public:
90+
91+ AsyncHttpHeaders () :
92+ _index (0 ),
93+ _last (std::numeric_limits<size_t >::max())
94+ {}
95+
96+ AsyncHttpHeaders (headers_t & headers) :
97+ _headers (headers),
98+ _index (0 ),
99+ _last (std::numeric_limits<size_t >::max())
100+ {}
101+
102+ void add (const header_t & header) {
103+ _headers.push_back (header);
104+ }
105+
106+ size_t size () {
107+ return _headers.size ();
108+ }
109+
110+ void reserve (size_t size) {
111+ _headers.reserve (size);
112+ }
113+
114+ bool has (const char * key) {
115+ for (const auto & header : _headers) {
116+ if (strcmp_P (key, header.key ()) == 0 ) return true ;
117+ }
118+ return false ;
119+ }
120+
121+ String& current () {
122+ if (_last == _index) return _value;
123+ if (_headers.size () && (_index < _headers.size ())) {
124+ const auto & current = _headers.at (_index);
125+ _value.reserve (
126+ current.keyLength ()
127+ + current.valueLength ()
128+ + strlen (" : \r\n " )
129+ );
130+
131+ _value = current.key ();
132+ _value += " : " ;
133+ _value += current.value ();
134+ _value += " \r\n " ;
135+ } else {
136+ _value = " " ;
137+ }
138+
139+ _last = _index;
140+
141+ return _value;
142+ }
143+
144+ String& next () {
145+ ++_index;
146+ return current ();
147+ }
148+
149+ bool done () {
150+ return (_index >= _headers.size ());
151+ }
152+
153+ void clear () {
154+ _index = 0 ;
155+ _last = std::numeric_limits<size_t >::max ();
156+ _headers.clear ();
157+ }
158+
159+ headers_t ::const_iterator begin () {
160+ return _headers.begin ();
161+ }
162+
163+ headers_t ::const_iterator end () {
164+ return _headers.end ();
165+ }
166+
167+ };
21168
22169struct AsyncHttpError {
23170
@@ -53,6 +200,9 @@ struct AsyncHttpError {
53200
54201class AsyncHttp {
55202
203+ constexpr const size_t DEFAULT_TIMEOUT = 5000 ;
204+ constexpr const size_t DEFAULT_PATH_BUFSIZE = 256 ;
205+
56206 public:
57207
58208 AsyncClient client;
@@ -92,18 +242,23 @@ class AsyncHttp {
92242 String method;
93243 String path;
94244
245+ // WebRequest.cpp
246+ // LinkedList<AsyncWebHeader*> headers;
247+ // std::vector<AsyncHttpHeader> headers;
248+ AsyncHttpHeaders headers;
249+
95250 String host;
96251 uint16_t port;
97252
98253 uint32_t ts;
99- uint32_t timeout = 5000 ;
254+ uint32_t timeout = DEFAULT_TIMEOUT ;
100255
101256 bool connected = false ;
102257 bool connecting = false ;
103258
104- // TODO: since we are single threaded, no need to buffer anything and we can directly use client->add with anything right in the body_send callback
259+ // TODO ref: https://github.com/xoseperez/espurna/pull/1909#issuecomment-533319480
260+ // since LWIP_NETIF_TX_SINGLE_PBUF is enabled, no need to buffer anything and we can directly use client->add with non-persistent data
105261 // buuut... this exposes asyncclient to the modules, maybe this needs a simple cbuf periodically flushing the data and this method simply filling it
106- // (ref: AsyncTCPBuffer class in ESPAsyncTCP or ESPAsyncWebServer chuncked response callback)
107262 void trySend () {
108263 if (!client.canSend ()) return ;
109264 if (!on_body_send) {
@@ -113,6 +268,29 @@ class AsyncHttp {
113268 on_body_send (this , &client);
114269 }
115270
271+ bool trySendHeaders () {
272+ if (headers.done ()) return true ;
273+
274+ const auto & string = headers.current ();
275+ const auto len = string.length ();
276+
277+ if (!len) {
278+ return true ;
279+ }
280+
281+ if (client.space () >= (len + 2 )) {
282+ if (client.add (string.c_str (), len)) {
283+ if (!headers.next ().length ()) {
284+ client.add (" \r\n " , 2 );
285+ }
286+ }
287+ client.send ();
288+ }
289+
290+ return false ;
291+ }
292+
293+
116294 protected:
117295
118296 static AsyncHttpError _timeoutError (AsyncHttpError::error_t error, const __FlashStringHelper* message, uint32_t ts) {
@@ -126,7 +304,7 @@ class AsyncHttp {
126304
127305 static void _onDisconnect (void * http_ptr, AsyncClient*) {
128306 AsyncHttp* http = static_cast <AsyncHttp*>(http_ptr);
129- if (http->on_disconnected ) http->on_disconnected (http);
307+ if (http->on_disconnected ) http->on_disconnected (http);
130308 http->ts = 0 ;
131309 http->connected = false ;
132310 http->connecting = false ;
@@ -244,50 +422,41 @@ class AsyncHttp {
244422
245423 if (http->on_connected ) http->on_connected (http);
246424
247- const int headers_len =
248- strlen_P ( HTTP_REQUEST_TEMPLATE )
249- + http->method . length ()
250- + http->host . length ()
251- + http-> path . length ()
252- + 32 ;
253-
254- int data_len = 0 ;
255- if (http-> cfg & HTTP_SEND ) {
256- if (! http->on_body_send ) {
257- ASYNC_HTTP_DEBUG ( " err | no send_body callback set \n " ) ;
258- client-> close ( true );
259- return ;
425+ {
426+ size_t data_len = 0 ;
427+ if ( http->cfg & HTTP_SEND ) {
428+ if (! http->on_body_send ) {
429+ ASYNC_HTTP_DEBUG ( " err | no send_body callback set \n " );
430+ client-> close ( true ) ;
431+ return ;
432+ }
433+ // XXX: ...class instead of this multi-function?
434+ data_len = http->on_body_send (http, nullptr );
435+ char data_buf[ 22 ] ;
436+ snprintf (data_buf, sizeof (data_buf), " %u " , data_len );
437+ http-> headers . add ({Headers:: CONTENT_LENGTH , data_buf}) ;
260438 }
261- // XXX: ...class instead of this multi-function?
262- data_len = http->on_body_send (http, nullptr );
263439 }
264440
265- char * headers = (char *) malloc (headers_len + 1 );
441+ {
442+ char buf[DEFAULT_PATH_BUFSIZE ] = {0 };
443+ int res = snprintf_P (
444+ buf, sizeof (buf), PSTR (" %s %s HTTP/1.1\r\n " ),
445+ http->method .c_str (), http->path .c_str ()
446+ );
266447
267- if (!headers ) {
268- ASYNC_HTTP_DEBUG (" err | alloc %u fail \n " , headers_len + 1 );
269- client->close (true );
270- return ;
271- }
448+ if ((res < 0 ) || ( static_cast < size_t >(res) > sizeof (buf)) ) {
449+ ASYNC_HTTP_DEBUG (" err | could not print initial line \n " );
450+ client->close (true );
451+ return ;
452+ }
272453
273- int res = snprintf_P (headers, headers_len + 1 ,
274- HTTP_REQUEST_TEMPLATE ,
275- http->method .c_str (),
276- http->path .c_str (),
277- http->host .c_str (),
278- data_len
279- );
280- if (res >= (headers_len + 1 )) {
281- ASYNC_HTTP_DEBUG (" err | res>=len :: %u>=%u\n " , res, headers_len + 1 );
282- free (headers);
283- client->close (true );
284- return ;
454+ client->add (buf, res);
285455 }
286456
287- client->write (headers);
288- free (headers);
289-
290- if (http->cfg & HTTP_SEND ) http->trySend ();
457+ if (http->trySendHeaders ()) {
458+ if (http->cfg & HTTP_SEND ) http->trySend ();
459+ }
291460 }
292461
293462 static void _onError (void * http_ptr, AsyncClient* client, err_t err) {
@@ -298,11 +467,15 @@ class AsyncHttp {
298467 static void _onAck (void * http_ptr, AsyncClient* client, size_t , uint32_t ) {
299468 AsyncHttp* http = static_cast <AsyncHttp*>(http_ptr);
300469 http->ts = millis ();
301- if (http->cfg & HTTP_SEND ) http->trySend ();
470+ if (http->trySendHeaders ()) {
471+ if (http->cfg & HTTP_SEND ) http->trySend ();
472+ }
302473 }
303474
475+
304476 public:
305- AsyncHttp () {
477+ AsyncHttp ()
478+ {
306479 client.onDisconnect (_onDisconnect, this );
307480 client.onTimeout (_onTimeout, this );
308481 client.onPoll (_onPoll, this );
@@ -326,22 +499,33 @@ class AsyncHttp {
326499 this ->ts = millis ();
327500
328501 // Treat every method as GET (receive-only), exception for POST / PUT to send data out
502+ size_t headers_size = 3 ;
329503 this ->cfg = HTTP_RECV ;
330504 if (this ->method .equals (" POST" ) || this ->method .equals (" PUT" )) {
331505 if (!this ->on_body_send ) return false ;
332506 this ->cfg = HTTP_SEND | HTTP_RECV ;
507+ headers_size += 2 ;
333508 }
334509
335- bool status = false ;
510+ headers.reserve (headers_size);
511+ headers.clear ();
512+
513+ headers.add ({Headers::HOST , this ->host .c_str ()});
514+ headers.add ({Headers::USER_AGENT , " ESPurna" });
515+ headers.add ({Headers::CONNECTION , " close" });
516+ if (this ->cfg & HTTP_SEND ) {
517+ headers.add ({Headers::CONTENT_TYPE , " application/x-www-form-urlencoded" });
518+ }
336519
520+ bool status = false ;
337521 #if ASYNC_TCP_SSL_ENABLED
338522 status = client.connect (this ->host .c_str (), this ->port , use_ssl);
339523 #else
340524 status = client.connect (this ->host .c_str (), this ->port );
341525 #endif
342526
343527 this ->connecting = status;
344-
528+
345529 if (!status) {
346530 client.close (true );
347531 }
0 commit comments