Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding support for Tranfer-Encoding: chunked streams #394

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 103 additions & 2 deletions src/AudioFileSourceHTTPStream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,44 @@ AudioFileSourceHTTPStream::AudioFileSourceHTTPStream()
pos = 0;
reconnectTries = 0;
saveURL[0] = 0;
next_chunk = 0;
}

AudioFileSourceHTTPStream::AudioFileSourceHTTPStream(const char *url)
{
saveURL[0] = 0;
reconnectTries = 0;
next_chunk = 0;
open(url);

}

bool AudioFileSourceHTTPStream::verifyCrlf()
{

uint8_t crlf[3];

client.read(crlf, 2);
crlf[2] = 0;

return !strncmp("\r\n", reinterpret_cast<const char*>(crlf), 2);
}

int AudioFileSourceHTTPStream::getChunkSize()
{
String length = client.readStringUntil('\r');
String lf = client.readStringUntil('\n');

unsigned int val = 0;
auto ret = sscanf(length.c_str(), "%x", &val);
if(ret)
{
return val;
}
else
{
return -1;
}
}

bool AudioFileSourceHTTPStream::open(const char *url)
Expand All @@ -44,12 +75,37 @@ bool AudioFileSourceHTTPStream::open(const char *url)
#ifndef ESP32
http.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS);
#endif
const char* headers[] = { "Transfer-Encoding" };
http.collectHeaders( headers, 1 );
int code = http.GET();
if (code != HTTP_CODE_OK) {
http.end();
cb.st(STATUS_HTTPFAIL, PSTR("Can't open HTTP request"));
return false;
}
if (http.hasHeader("Transfer-Encoding")) {
audioLogger->printf_P(PSTR("Transfer-Encoding: %s\n"), http.header("Transfer-Encoding").c_str());
if(http.header("Transfer-Encoding") == String(PSTR("chunked"))) {

next_chunk = getChunkSize();
if(-1 == next_chunk)
{
return false;
}
is_chunked = true;
readImpl = &AudioFileSourceHTTPStream::readChunked;
} else {
is_chunked = false;
readImpl = &AudioFileSourceHTTPStream::readRegular;
}

} else {
readImpl = &AudioFileSourceHTTPStream::readRegular;
audioLogger->printf_P(PSTR("No Transfer-Encoding\n"));
is_chunked = false;
}


size = http.getSize();
strncpy(saveURL, url, sizeof(saveURL));
saveURL[sizeof(saveURL)-1] = 0;
Expand All @@ -61,13 +117,57 @@ AudioFileSourceHTTPStream::~AudioFileSourceHTTPStream()
http.end();
}

uint32_t AudioFileSourceHTTPStream::readRegular(void *data, uint32_t len, bool nonBlock)
{
return readInternal(data, len, nonBlock);
}

uint32_t AudioFileSourceHTTPStream::readChunked(void *data, uint32_t len, bool nonBlock)
{
uint32_t bytesRead = 0;
uint32_t pos = 0;

while(len > 0)
{
if(len >= next_chunk)
{
while (next_chunk)
{
bytesRead = readInternal(data + pos, next_chunk, nonBlock);
next_chunk -= bytesRead;
pos += bytesRead;
}
len -= pos;
if(!verifyCrlf())
{
audioLogger->printf(PSTR("Couldn't read CRLF after chunk, something is wrong !!\n"));
return 0;
}
next_chunk = getChunkSize();
}
else
{
bytesRead = readInternal(data + pos, len, nonBlock);
next_chunk -= bytesRead;
len -= bytesRead;
pos += bytesRead;
}

}


return pos;
}

uint32_t AudioFileSourceHTTPStream::read(void *data, uint32_t len)
{
if (data==NULL) {
audioLogger->printf_P(PSTR("ERROR! AudioFileSourceHTTPStream::read passed NULL data\n"));
return 0;
}
return readInternal(data, len, false);

return (this->*readImpl)(data, len, false);

}

uint32_t AudioFileSourceHTTPStream::readNonBlock(void *data, uint32_t len)
Expand All @@ -76,7 +176,8 @@ uint32_t AudioFileSourceHTTPStream::readNonBlock(void *data, uint32_t len)
audioLogger->printf_P(PSTR("ERROR! AudioFileSourceHTTPStream::readNonBlock passed NULL data\n"));
return 0;
}
return readInternal(data, len, true);
return (this->*readImpl)(data, len, true);

}

uint32_t AudioFileSourceHTTPStream::readInternal(void *data, uint32_t len, bool nonBlock)
Expand Down
15 changes: 14 additions & 1 deletion src/AudioFileSourceHTTPStream.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
#if defined(ESP32) || defined(ESP8266)
#pragma once

#include <map> // std::map<char, int> ascii_to_hex;
yoav-klein marked this conversation as resolved.
Show resolved Hide resolved

#include <Arduino.h>
#ifdef ESP32
#include <HTTPClient.h>
Expand All @@ -29,6 +31,8 @@
#endif
#include "AudioFileSource.h"



class AudioFileSourceHTTPStream : public AudioFileSource
{
friend class AudioFileSourceICYStream;
Expand All @@ -52,14 +56,23 @@ class AudioFileSourceHTTPStream : public AudioFileSource
enum { STATUS_HTTPFAIL=2, STATUS_DISCONNECTED, STATUS_RECONNECTING, STATUS_RECONNECTED, STATUS_NODATA };

private:
virtual uint32_t readInternal(void *data, uint32_t len, bool nonBlock);
bool is_chunked;
std::size_t next_chunk;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure of the reason for std::size_t vs. the well-known stdint.h size_t?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it really matter?

WiFiClient client;
HTTPClient http;
int pos;
int size;
int reconnectTries;
int reconnectDelayMs;
char saveURL[128];
uint32_t (AudioFileSourceHTTPStream::*readImpl)(void *data, uint32_t len, bool nonBlock);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the AudioFileSourceHTTPStream:: is superfluous here. We're already in class AudioFileSourceHTTPStream...

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what I've checked, this is how it can be used. You're welcomed to further check it yourself.


virtual uint32_t readInternal(void *data, uint32_t len, bool nonBlock);
uint32_t readChunked(void *data, uint32_t len, bool nonBlock);
uint32_t readRegular(void *data, uint32_t len, bool nonBlock);
bool verifyCrlf();
int getChunkSize();

};


Expand Down