Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save neerajkanhere/30559100be4c564bb34bfe45ba92e86f to your computer and use it in GitHub Desktop.
Save neerajkanhere/30559100be4c564bb34bfe45ba92e86f to your computer and use it in GitHub Desktop.
Poco::Net::HTTPRequestHandler subclass for sending file ranges, as needed for HTTP-based streaming.
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