mathieucarbou / ESPAsyncWebServer

Asynchronous HTTP and WebSocket Server Library for ESP32, ESP8266 and RP2040
https://mathieu.carbou.me/ESPAsyncWebServer/
GNU Lesser General Public License v3.0
87 stars 17 forks source link

[Q] how to do redirects from v3.3.0 on #112

Closed lumapu closed 1 month ago

lumapu commented 1 month ago

Today I tested several versions of your library and found out that v3.3.0 had a breaking change regarding headers.

I exactly use the redirect-code which is in the readme of this library: https://github.com/mathieucarbou/ESPAsyncWebServer?tab=readme-ov-file#redirect-to-another-url

This works fine with v3.2.4, but isn't functional any more with v.3.3.0 and newer.

The comparison of the headers provided by both versions can be seen in the picture below (left v3.24, right v3.3.0)

grafik

mathieucarbou commented 1 month ago

thanks! will have a look.

mathieucarbou commented 1 month ago

Hello, just tested with latest version:

  // curl -v -X GET http://192.168.4.1/redirect
  server.on("/redirect", HTTP_GET, [](AsyncWebServerRequest* request) {
    request->redirect("/");
  });

output:

❯  curl -v -X GET http://192.168.4.1/redirect
Note: Unnecessary use of -X or --request, GET is already inferred.
*   Trying 192.168.4.1:80...
* Connected to 192.168.4.1 () port 80
> GET /redirect HTTP/1.1
> Host: 192.168.4.1
> User-Agent: curl/8.10.0
> Accept: */*
> 
* Request completely sent off
< HTTP/1.1 302 Found
< Connection: close
< Location: /
< Accept-Ranges: none
< Content-Length: 0
< 
* shutting down connection #0
mathieucarbou commented 1 month ago

and:

  // curl -v -X GET -H "origin: http://192.168.4.1" http://192.168.4.1/redirect
  // curl -v -X POST -H "origin: http://192.168.4.1" http://192.168.4.1/redirect
  server.on("/redirect", HTTP_GET | HTTP_POST, [](AsyncWebServerRequest* request) {
    request->redirect("/");
  });
❯  curl -v -X GET -H "origin: http://192.168.4.1" http://192.168.4.1/redirect
Note: Unnecessary use of -X or --request, GET is already inferred.
*   Trying 192.168.4.1:80...
* Connected to 192.168.4.1 () port 80
> GET /redirect HTTP/1.1
> Host: 192.168.4.1
> User-Agent: curl/8.10.0
> Accept: */*
> origin: http://192.168.4.1
> 
* Request completely sent off
< HTTP/1.1 302 Found
< Connection: close
< Location: /
< Access-Control-Allow-Origin: http://192.168.4.1
< Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE
< Access-Control-Allow-Headers: X-Custom-Header
< Access-Control-Allow-Credentials: false
< Access-Control-Max-Age: 600
< Accept-Ranges: none
< Content-Length: 0
< 
* shutting down connection #0

and

❯  curl -v -X POST -H "origin: http://192.168.4.1" http://192.168.4.1/redirect
*   Trying 192.168.4.1:80...
* Connected to 192.168.4.1 () port 80
> POST /redirect HTTP/1.1
> Host: 192.168.4.1
> User-Agent: curl/8.10.0
> Accept: */*
> origin: http://192.168.4.1
> 
* Request completely sent off
< HTTP/1.1 302 Found
< Connection: close
< Location: /
< Access-Control-Allow-Origin: http://192.168.4.1
< Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE
< Access-Control-Allow-Headers: X-Custom-Header
< Access-Control-Allow-Credentials: false
< Access-Control-Max-Age: 600
< Accept-Ranges: none
< Content-Length: 0
< 
* shutting down connection #0

So it works on my side.

Do you have a middleware somewhere sending this 200 OK response ?

lumapu commented 1 month ago

I can't confirm that.

related Code ```cpp inline void checkRedirect(AsyncWebServerRequest *request) { if ((mConfig->sys.protectionMask & PROT_MASK_INDEX) != PROT_MASK_INDEX) request->redirect(F("/index")); else if ((mConfig->sys.protectionMask & PROT_MASK_LIVE) != PROT_MASK_LIVE) request->redirect(F("/live")); else if ((mConfig->sys.protectionMask & PROT_MASK_HISTORY) != PROT_MASK_HISTORY) request->redirect(F("/history")); else if ((mConfig->sys.protectionMask & PROT_MASK_SERIAL) != PROT_MASK_SERIAL) request->redirect(F("/serial")); else if ((mConfig->sys.protectionMask & PROT_MASK_SYSTEM) != PROT_MASK_SYSTEM) request->redirect(F("/system")); else request->redirect(F("/login")); } void checkProtection(AsyncWebServerRequest *request) { if(mApp->isProtected(request->client()->remoteIP().toString().c_str(), "", true)) { checkRedirect(request); return; } } void getPage(AsyncWebServerRequest *request, uint16_t mask, const uint8_t *zippedHtml, uint32_t len) { if (CHECK_MASK(mConfig->sys.protectionMask, mask)) checkProtection(request); AsyncWebServerResponse *response = beginResponse(request, 200, F("text/html; charset=UTF-8"), zippedHtml, len); response->addHeader(F("Content-Encoding"), "gzip"); response->addHeader(F("content-type"), "text/html; charset=UTF-8"); if(request->hasParam("v")) response->addHeader(F("Cache-Control"), F("max-age=604800")); request->send(response); } void onIndex(AsyncWebServerRequest *request) { getPage(request, PROT_MASK_INDEX, index_html, index_html_len); } ```

v3.2.4

curl -v -X GET -H "origin: http://10.20.3.47" http://10.20.3.47/
Note: Unnecessary use of -X or --request, GET is already inferred.
*   Trying 10.20.3.47...
* TCP_NODELAY set
* Connected to 10.20.3.47 (10.20.3.47) port 80 (#0)
> GET / HTTP/1.1
> Host: 10.20.3.47
> User-Agent: curl/7.58.0
> Accept: */*
> origin: http://10.20.3.47
>
< HTTP/1.1 302 Found
< Connection: close
< Location: /live
< Accept-Ranges: none
< Content-Length: 0
<
* Closing connection 0

v3.3.0

curl -v -X GET -H "origin: http://10.20.3.47" http://10.20.3.47/
Note: Unnecessary use of -X or --request, GET is already inferred.
*   Trying 10.20.3.47...
* TCP_NODELAY set
* Connected to 10.20.3.47 (10.20.3.47) port 80 (#0)
> GET / HTTP/1.1
> Host: 10.20.3.47
> User-Agent: curl/7.58.0
> Accept: */*
> origin: http://10.20.3.47
>
< HTTP/1.1 200 OK
< Content-Encoding: gzip
< content-type: text/html; charset=UTF-8
< Connection: close
< Accept-Ranges: none
< Content-Length: 3102
<
Warning: Binary output can mess up your terminal. Use "--output -" to tell
Warning: curl to output it to your terminal anyway, or consider "--output
Warning: <FILE>" to save to a file.
* Failed writing body (0 != 3102)
* stopped the pause stream!
* Closing connection 0
lumapu commented 1 month ago

I found the difference. I was creating a AsyncWebServerResponse from the request even if the redirect was set. With library verison up to 3.2.4 it worked like that, from 3.3.0 on you have to directly skip all changes to request.

AsyncWebServerResponse *response = beginResponse(request, 200, F("text/html; charset=UTF-8"), zippedHtml, len);

For me it's solved, I don't know how you feel about that - maybe I used the library wrong all the time long, but with a recent change the 'wrong' behavior is not supported any more.

mathieucarbou commented 1 month ago

@lumapu : I don't really understand what you mean... You mean you have created 2 responses ?

Note: I think I may understand what you mean, but just to be sure...

beginResponse() as a static method is also not available...

Could you please share a bit more about your code, before, and now ?

Thanks!

FYI:

This was indeed invalid to create 2 responses in the same handler before because the next ones would be ignored since the previous one was committed to the network already. Sadly the library was silently discarding and was not behaving properly by accepting a subsequent request and ignoring it.

Now, middleware requires the ability to replace a response (See https://github.com/mathieucarbou/ESPAsyncWebServer/releases/tag/v3.3.0). So only the last created response will be considered.

The code behaviour is now correct and just highlights the mistakes left in the codebase.