Created
March 13, 2015 05:46
-
-
Save obiltschnig/dc2e90643e6fabe7d938 to your computer and use it in GitHub Desktop.
Poco::Net::HTTPRequestHandler subclass for sending file ranges, as needed for HTTP-based streaming.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class MediaRequestHandler: public Poco::Net::HTTPRequestHandler | |
{ | |
public: | |
enum | |
{ | |
BUFFER_SIZE = 8192 | |
}; | |
MediaRequestHandler(const std::string& mediaPath): | |
_mediaPath(mediaPath) | |
{ | |
} | |
void handleRequest(Poco::Net::HTTPServerRequest& request, Poco::Net::HTTPServerResponse& response) | |
{ | |
std::string decodedPath; | |
Poco::URI::decode(request.getURI(), decodedPath); | |
Poco::Path requestPath(decodedPath, Poco::Path::PATH_UNIX); | |
Poco::Path mediaPath(_mediaPath); | |
mediaPath.makeDirectory(); | |
for (int i = 1; i < requestPath.depth(); i++) | |
{ | |
mediaPath.pushDirectory(requestPath[i]); | |
} | |
mediaPath.setFileName(requestPath.getFileName()); | |
Poco::File mediaFile(mediaPath.toString()); | |
if (mediaFile.path().find("..") != std::string::npos) | |
{ | |
response.setStatusAndReason(Poco::Net::HTTPResponse::HTTP_BAD_REQUEST); | |
response.send(); | |
} | |
if (mediaFile.exists()) | |
{ | |
Poco::Timestamp dateTime = mediaFile.getLastModified(); | |
Poco::File::FileSize length = mediaFile.getSize(); | |
std::string contentType = ContentDirectory1Impl::contentTypeForExtension(mediaPath.getExtension()); | |
if (request.getMethod() == Poco::Net::HTTPRequest::HTTP_HEAD || request.getMethod() == Poco::Net::HTTPRequest::HTTP_GET) | |
{ | |
response.set("Last-Modified", Poco::DateTimeFormatter::format(dateTime, Poco::DateTimeFormat::HTTP_FORMAT)); | |
response.set("Accept-Ranges", "bytes"); | |
response.setContentType(contentType); | |
} | |
if (request.getMethod() == Poco::Net::HTTPRequest::HTTP_HEAD) | |
{ | |
response.setContentLength64(length); | |
response.send(); | |
} | |
else if (request.getMethod() == Poco::Net::HTTPRequest::HTTP_GET) | |
{ | |
if (request.has("Range")) | |
{ | |
Poco::File::FileSize rangeStart = 0; | |
Poco::File::FileSize rangeLength = 0; | |
if (parseRange(request.get("Range"), length, rangeStart, rangeLength)) | |
{ | |
sendFileRange(response, mediaPath.toString(), length, rangeStart, rangeLength); | |
} | |
else | |
{ | |
response.setStatusAndReason(Poco::Net::HTTPResponse::HTTP_REQUESTED_RANGE_NOT_SATISFIABLE); | |
response.send(); | |
} | |
} | |
else | |
{ | |
response.sendFile(mediaPath.toString(), contentType); | |
} | |
} | |
else | |
{ | |
response.setStatusAndReason(Poco::Net::HTTPResponse::HTTP_METHOD_NOT_ALLOWED); | |
response.send(); | |
} | |
} | |
else | |
{ | |
response.setStatusAndReason(Poco::Net::HTTPResponse::HTTP_NOT_FOUND); | |
response.send(); | |
} | |
} | |
bool parseRange(const std::string& range, Poco::File::FileSize fileSize, Poco::File::FileSize& rangeStart, Poco::File::FileSize& rangeLength) | |
{ | |
std::string::const_iterator it = range.begin(); | |
std::string::const_iterator end = range.end(); | |
while (it != end && Poco::Ascii::isSpace(*it)) ++it; | |
std::string unit; | |
while (it != end && *it != '=') unit += *it++; | |
if (unit == "bytes" && it != end) | |
{ | |
++it; | |
if (it != end && *it == '-') | |
{ | |
++it; | |
rangeLength = 0; | |
while (it != end && Poco::Ascii::isDigit(*it)) | |
{ | |
rangeLength *= 10; | |
rangeLength += *it - '0'; | |
++it; | |
} | |
rangeStart = fileSize - rangeLength; | |
return true; | |
} | |
else if (it != end && Poco::Ascii::isDigit(*it)) | |
{ | |
rangeStart = 0; | |
while (it != end && Poco::Ascii::isDigit(*it)) | |
{ | |
rangeStart *= 10; | |
rangeStart += *it - '0'; | |
++it; | |
} | |
if (it != end && *it == '-') | |
{ | |
++it; | |
if (it != end) | |
{ | |
rangeLength = 0; | |
while (it != end && Poco::Ascii::isDigit(*it)) | |
{ | |
rangeLength *= 10; | |
rangeLength += *it - '0'; | |
++it; | |
} | |
rangeLength = rangeLength - rangeStart + 1; | |
} | |
else | |
{ | |
rangeLength = fileSize - rangeStart; | |
} | |
return true; | |
} | |
} | |
} | |
return false; | |
} | |
void sendFileRange(Poco::Net::HTTPServerResponse& response, const std::string& path, Poco::File::FileSize length, Poco::File::FileSize rangeStart, Poco::File::FileSize rangeLength) | |
{ | |
response.setStatusAndReason(Poco::Net::HTTPResponse::HTTP_PARTIAL_CONTENT); | |
response.setContentLength64(rangeLength); | |
response.set("Content-Range", Poco::format("bytes %?d-%?d/%?d", rangeStart, rangeStart + rangeLength - 1, length)); | |
std::ostream& ostr = response.send(); | |
Poco::FileInputStream istr(path); | |
istr.seekg(static_cast<std::streampos>(rangeStart)); | |
Poco::Buffer<char> buffer(BUFFER_SIZE); | |
while (rangeLength > 0) | |
{ | |
std::streamsize chunk = BUFFER_SIZE; | |
if (chunk > rangeLength) chunk = static_cast<std::streamsize>(rangeLength); | |
istr.read(buffer.begin(), chunk); | |
std::streamsize n = istr.gcount(); | |
if (n == 0) break; | |
ostr.write(buffer.begin(), n); | |
rangeLength -= n; | |
} | |
} | |
private: | |
std::string _mediaPath; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment