Add HTTP/1.1 chunked response and allow stream and callback responses to not have content length

ContentLength and ContentType have setters (before you send the
response)
This commit is contained in:
Me No Dev
2016-01-27 18:46:49 +02:00
parent 4a5f64de03
commit 9296156ab5
4 changed files with 114 additions and 22 deletions

View File

@@ -57,6 +57,15 @@ class AsyncCallbackResponse: public AsyncAbstractResponse {
size_t _fillBuffer(uint8_t *buf, size_t maxLen); size_t _fillBuffer(uint8_t *buf, size_t maxLen);
}; };
class AsyncChunkedResponse: public AsyncAbstractResponse {
private:
AwsResponseFiller _content;
public:
AsyncChunkedResponse(String contentType, AwsResponseFiller callback);
bool _sourceValid(){ return !!(_content); }
size_t _fillBuffer(uint8_t *buf, size_t maxLen);
};
class cbuf; class cbuf;
class AsyncResponseStream: public AsyncAbstractResponse, public Print { class AsyncResponseStream: public AsyncAbstractResponse, public Print {

View File

@@ -86,6 +86,7 @@ class AsyncWebServerRequest {
String _temp; String _temp;
uint8_t _parseState; uint8_t _parseState;
uint8_t _version;
WebRequestMethod _method; WebRequestMethod _method;
String _url; String _url;
String _host; String _host;
@@ -142,6 +143,7 @@ class AsyncWebServerRequest {
~AsyncWebServerRequest(); ~AsyncWebServerRequest();
AsyncClient* client(){ return _client; } AsyncClient* client(){ return _client; }
uint8_t version(){ return _version; }
WebRequestMethod method(){ return _method; } WebRequestMethod method(){ return _method; }
String url(){ return _url; } String url(){ return _url; }
String host(){ return _host; } String host(){ return _host; }
@@ -166,6 +168,7 @@ class AsyncWebServerRequest {
AsyncWebServerResponse *beginResponse(FS &fs, String path, String contentType=String(), bool download=false); AsyncWebServerResponse *beginResponse(FS &fs, String path, String contentType=String(), bool download=false);
AsyncWebServerResponse *beginResponse(Stream &stream, String contentType, size_t len); AsyncWebServerResponse *beginResponse(Stream &stream, String contentType, size_t len);
AsyncWebServerResponse *beginResponse(String contentType, size_t len, AwsResponseFiller callback); AsyncWebServerResponse *beginResponse(String contentType, size_t len, AwsResponseFiller callback);
AsyncWebServerResponse *beginChunkedResponse(String contentType, AwsResponseFiller callback);
AsyncResponseStream *beginResponseStream(String contentType, size_t len, size_t bufferSize=1460); AsyncResponseStream *beginResponseStream(String contentType, size_t len, size_t bufferSize=1460);
int headers(); // get header count int headers(); // get header count
@@ -221,6 +224,8 @@ class AsyncWebServerResponse {
AsyncWebHeader *_headers; AsyncWebHeader *_headers;
String _contentType; String _contentType;
size_t _contentLength; size_t _contentLength;
bool _sendContentLength;
bool _chunked;
size_t _headLength; size_t _headLength;
size_t _sentLength; size_t _sentLength;
size_t _ackedLength; size_t _ackedLength;
@@ -230,8 +235,10 @@ class AsyncWebServerResponse {
public: public:
AsyncWebServerResponse(); AsyncWebServerResponse();
virtual ~AsyncWebServerResponse(); virtual ~AsyncWebServerResponse();
virtual void setContentLength(size_t len);
virtual void setContentType(String type);
virtual void addHeader(String name, String value); virtual void addHeader(String name, String value);
virtual String _assembleHead(); virtual String _assembleHead(uint8_t version);
virtual bool _finished(); virtual bool _finished();
virtual bool _failed(); virtual bool _failed();
virtual void _respond(AsyncWebServerRequest *request); virtual void _respond(AsyncWebServerRequest *request);

View File

@@ -52,14 +52,16 @@ const char* AsyncWebServerResponse::_responseCodeToString(int code) {
} }
AsyncWebServerResponse::AsyncWebServerResponse() AsyncWebServerResponse::AsyncWebServerResponse()
:_code(0) : _code(0)
,_headers(NULL) , _headers(NULL)
,_contentType() , _contentType()
,_contentLength(0) , _contentLength(0)
,_headLength(0) , _sendContentLength(true)
,_sentLength(0) , _chunked(false)
,_ackedLength(0) , _headLength(0)
,_state(RESPONSE_SETUP) , _sentLength(0)
, _ackedLength(0)
, _state(RESPONSE_SETUP)
{ {
addHeader("Connection","close"); addHeader("Connection","close");
addHeader("Access-Control-Allow-Origin","*"); addHeader("Access-Control-Allow-Origin","*");
@@ -73,6 +75,16 @@ AsyncWebServerResponse::~AsyncWebServerResponse(){
} }
} }
void AsyncWebServerResponse::setContentLength(size_t len){
if(_state == RESPONSE_SETUP)
_contentLength = len;
}
void AsyncWebServerResponse::setContentType(String type){
if(_state == RESPONSE_SETUP)
_contentType = type;
}
void AsyncWebServerResponse::addHeader(String name, String value){ void AsyncWebServerResponse::addHeader(String name, String value){
AsyncWebHeader *header = new AsyncWebHeader(name, value); AsyncWebHeader *header = new AsyncWebHeader(name, value);
if(_headers == NULL){ if(_headers == NULL){
@@ -84,12 +96,19 @@ void AsyncWebServerResponse::addHeader(String name, String value){
} }
} }
String AsyncWebServerResponse::_assembleHead(){ String AsyncWebServerResponse::_assembleHead(uint8_t version){
String out = "HTTP/1.1 " + String(_code) + " " + _responseCodeToString(_code) + "\r\n"; if(version){
out += "Content-Length: " + String(_contentLength) + "\r\n"; addHeader("Accept-Ranges","none");
if(_contentType.length()){ if(_chunked)
out += "Content-Type: " + _contentType + "\r\n"; addHeader("Transfer-Encoding","chunked");
} }
String out = "HTTP/1." + String(version) + " " + String(_code) + " " + _responseCodeToString(_code) + "\r\n";
if(_sendContentLength)
out += "Content-Length: " + String(_contentLength) + "\r\n";
if(_contentType.length())
out += "Content-Type: " + _contentType + "\r\n";
AsyncWebHeader *h; AsyncWebHeader *h;
while(_headers != NULL){ while(_headers != NULL){
h = _headers; h = _headers;
@@ -123,7 +142,7 @@ AsyncBasicResponse::AsyncBasicResponse(int code, String contentType, String cont
void AsyncBasicResponse::_respond(AsyncWebServerRequest *request){ void AsyncBasicResponse::_respond(AsyncWebServerRequest *request){
_state = RESPONSE_HEADERS; _state = RESPONSE_HEADERS;
String out = _assembleHead(); String out = _assembleHead(request->version());
size_t outLen = out.length(); size_t outLen = out.length();
size_t space = request->client()->space(); size_t space = request->client()->space();
if(!_contentLength && space >= outLen){ if(!_contentLength && space >= outLen){
@@ -192,7 +211,7 @@ void AsyncAbstractResponse::_respond(AsyncWebServerRequest *request){
request->send(500); request->send(500);
return; return;
} }
_head = _assembleHead(); _head = _assembleHead(request->version());
_state = RESPONSE_HEADERS; _state = RESPONSE_HEADERS;
size_t outLen = _head.length(); size_t outLen = _head.length();
size_t space = request->client()->space(); size_t space = request->client()->space();
@@ -217,18 +236,42 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, u
_ackedLength += len; _ackedLength += len;
size_t space = request->client()->space(); size_t space = request->client()->space();
if(_state == RESPONSE_CONTENT){ if(_state == RESPONSE_CONTENT){
size_t remaining = _contentLength - _sentLength; size_t outLen;
size_t outLen = (remaining > space)?space:remaining; size_t readLen = 0;
if(_chunked || !_sendContentLength){
outLen = space;
} else {
size_t remaining = _contentLength - _sentLength;
outLen = (remaining > space)?space:remaining;
}
uint8_t *buf = (uint8_t *)malloc(outLen); uint8_t *buf = (uint8_t *)malloc(outLen);
outLen = _fillBuffer(buf, outLen);
if(_chunked){
readLen = _fillBuffer(buf, outLen - 8);
char pre[6];
sprintf(pre, "%x\r\n", readLen);
size_t preLen = strlen(pre);
memmove(buf+preLen, buf, preLen);
for(size_t i=0; i<preLen; i++)
buf[i] = pre[i];
outLen = preLen + readLen;
buf[outLen++] = '\r';
buf[outLen++] = '\n';
} else {
outLen = _fillBuffer(buf, outLen);
}
if(outLen) if(outLen)
request->client()->write((const char*)buf, outLen); outLen = request->client()->write((const char*)buf, outLen);
_sentLength += outLen; _sentLength += outLen;
free(buf); free(buf);
if(_sentLength == _contentLength){
if((_chunked && readLen == 0) || (!_sendContentLength && outLen == 0) || _sentLength == _contentLength){
_state = RESPONSE_WAIT_ACK; _state = RESPONSE_WAIT_ACK;
} }
return outLen; return outLen;
} else if(_state == RESPONSE_HEADERS){ } else if(_state == RESPONSE_HEADERS){
size_t outLen = _head.length(); size_t outLen = _head.length();
if(space >= outLen){ if(space >= outLen){
@@ -243,8 +286,10 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, u
return out.length(); return out.length();
} }
} else if(_state == RESPONSE_WAIT_ACK){ } else if(_state == RESPONSE_WAIT_ACK){
if(_ackedLength >= (_headLength+_contentLength)){ if(!_sendContentLength || _ackedLength >= (_headLength+_contentLength)){
_state = RESPONSE_END; _state = RESPONSE_END;
if(!_chunked && !_sendContentLength)
request->client()->close();
} }
} }
return 0; return 0;
@@ -329,6 +374,8 @@ AsyncCallbackResponse::AsyncCallbackResponse(String contentType, size_t len, Aws
_code = 200; _code = 200;
_content = callback; _content = callback;
_contentLength = len; _contentLength = len;
if(!len)
_sendContentLength = false;
_contentType = contentType; _contentType = contentType;
} }
@@ -336,6 +383,23 @@ size_t AsyncCallbackResponse::_fillBuffer(uint8_t *data, size_t len){
return _content(data, len); return _content(data, len);
} }
/*
* Chunked Response
* */
AsyncChunkedResponse::AsyncChunkedResponse(String contentType, AwsResponseFiller callback){
_code = 200;
_content = callback;
_contentLength = 0;
_contentType = contentType;
_sendContentLength = false;
_chunked = true;
}
size_t AsyncChunkedResponse::_fillBuffer(uint8_t *data, size_t len){
return _content(data, len);
}
/* /*
* Response Stream (You can print/write/printf to it, up to the contentLen bytes) * Response Stream (You can print/write/printf to it, up to the contentLen bytes)
@@ -344,6 +408,8 @@ size_t AsyncCallbackResponse::_fillBuffer(uint8_t *data, size_t len){
AsyncResponseStream::AsyncResponseStream(String contentType, size_t len, size_t bufferSize){ AsyncResponseStream::AsyncResponseStream(String contentType, size_t len, size_t bufferSize){
_code = 200; _code = 200;
_contentLength = len; _contentLength = len;
if(!len)
_sendContentLength = false;
_contentType = contentType; _contentType = contentType;
_content = new cbuf(bufferSize); _content = new cbuf(bufferSize);
} }

View File

@@ -25,6 +25,7 @@ AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer* s, AsyncClient* c)
, _interestingHeaders(new StringArray()) , _interestingHeaders(new StringArray())
, _temp() , _temp()
, _parseState(0) , _parseState(0)
, _version(0)
, _method(HTTP_ANY) , _method(HTTP_ANY)
, _url() , _url()
, _host() , _host()
@@ -229,6 +230,9 @@ bool AsyncWebServerRequest::_parseReqHead(){
} }
} }
_temp = _temp.substring(_temp.indexOf(' ')+1);
if(_temp.startsWith("HTTP/1.1"))
_version = 1;
_temp = String(); _temp = String();
return true; return true;
} }
@@ -620,6 +624,12 @@ AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(String contentType
return new AsyncCallbackResponse(contentType, len, callback); return new AsyncCallbackResponse(contentType, len, callback);
} }
AsyncWebServerResponse * AsyncWebServerRequest::beginChunkedResponse(String contentType, AwsResponseFiller callback){
if(_version)
return new AsyncChunkedResponse(contentType, callback);
return new AsyncCallbackResponse(contentType, 0, callback);
}
AsyncResponseStream * AsyncWebServerRequest::beginResponseStream(String contentType, size_t len, size_t bufferSize){ AsyncResponseStream * AsyncWebServerRequest::beginResponseStream(String contentType, size_t len, size_t bufferSize){
return new AsyncResponseStream(contentType, len, bufferSize); return new AsyncResponseStream(contentType, len, bufferSize);
} }