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 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
117 changes: 115 additions & 2 deletions src/AudioFileSourceHTTPStream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,58 @@

AudioFileSourceHTTPStream::AudioFileSourceHTTPStream()
{
// init_ascii_to_hex();
pos = 0;
reconnectTries = 0;
saveURL[0] = 0;
next_chunk = 0;
}

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

}

int AudioFileSourceHTTPStream::convertHexToInt(const char *str)
yoav-klein marked this conversation as resolved.
Show resolved Hide resolved
{
int result = 0;
while(*str)
{
if(!((*str <= '9' && *str >= '0') || (*str <= 'f' && *str >= 'a')))
{
audioLogger->printf("Character not HEX");
return -1;
}
result = (result << 4) | ascii_to_hex[*str];
++str;
}

return result;
}

bool AudioFileSourceHTTPStream::verifyCrlf()
{

uint8_t crlf[3];

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

String crlfString = "\r\n";
yoav-klein marked this conversation as resolved.
Show resolved Hide resolved
return crlfString == String((char*)crlf);
}

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

return convertHexToInt(length.c_str());
}

bool AudioFileSourceHTTPStream::open(const char *url)
Expand All @@ -44,12 +86,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("chunked")) {
yoav-klein marked this conversation as resolved.
Show resolved Hide resolved

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 +128,58 @@ 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("Couldn't read CRLF after chunk, something is wrong !!\n");
yoav-klein marked this conversation as resolved.
Show resolved Hide resolved
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 readInternal(data, len, false);
yoav-klein marked this conversation as resolved.
Show resolved Hide resolved

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

}

uint32_t AudioFileSourceHTTPStream::readNonBlock(void *data, uint32_t len)
Expand All @@ -76,7 +188,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
34 changes: 33 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,42 @@ 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 convertHexToInt(const char *str);
int getChunkSize();

std::map<char, unsigned char> ascii_to_hex = {
yoav-klein marked this conversation as resolved.
Show resolved Hide resolved
{ '0', 0 },
{ '1', 1 },
{ '2', 2 },
{ '3', 3 },
{ '4', 4 },
{ '5', 5 },
{ '6', 6 },
{ '7', 7 },
{ '8', 8 },
{ '9', 9 },
{ 'a', 10 },
{ 'b', 11 },
{ 'c', 12 },
{ 'd', 13 },
{ 'e', 14 },
{ 'f', 15 },
};
};


Expand Down