forked from j62/ctbrec
1
0
Fork 0

Add byte range support for HTTP requests

This commit is contained in:
0xboobface 2019-12-08 18:37:26 +01:00
parent cb90ff8264
commit 5ce2bd9901
1 changed files with 66 additions and 30 deletions

View File

@ -2,11 +2,13 @@ package ctbrec.recorder.server;
import static javax.servlet.http.HttpServletResponse.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Enumeration;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
@ -15,16 +17,13 @@ import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.iheartradio.m3u8.ParseException;
import com.iheartradio.m3u8.PlaylistException;
import ctbrec.Config;
public class HlsServlet extends AbstractCtbrecServlet {
private static final transient Logger LOG = LoggerFactory.getLogger(HlsServlet.class);
private Config config;
private final Config config;
public HlsServlet(Config config) {
this.config = config;
@ -42,34 +41,38 @@ public class HlsServlet extends AbstractCtbrecServlet {
try {
boolean isRequestAuthenticated = checkAuthentication(req, req.getRequestURI());
if (!isRequestAuthenticated) {
resp.setStatus(SC_UNAUTHORIZED);
String response = "{\"status\": \"error\", \"msg\": \"HMAC does not match\"}";
resp.getWriter().write(response);
writeResponse(resp, SC_UNAUTHORIZED, "{\"status\": \"error\", \"msg\": \"HMAC does not match\"}");
return;
}
} catch (InvalidKeyException | NoSuchAlgorithmException | IllegalStateException e1) {
resp.setStatus(SC_UNAUTHORIZED);
String response = "{\"status\": \"error\", \"msg\": \"Authentication failed\"}";
resp.getWriter().write(response);
writeResponse(resp, SC_UNAUTHORIZED, "{\"status\": \"error\", \"msg\": \"Authentication failed\"}");
return;
}
try {
servePlaylist(req, resp, requestedFile);
} catch (ParseException | PlaylistException e) {
LOG.error("Error while generating playlist file", e);
throw new IOException("Couldn't generate playlist file " + requestedFile, e);
}
servePlaylist(req, resp, requestedFile);
} else {
if (requestedFile.exists()) {
Enumeration<String> headerNames = req.getHeaderNames();
while(headerNames.hasMoreElements()) {
String header = headerNames.nextElement();
LOG.info("{}: {}", header, req.getHeader(header));
}
serveSegment(req, resp, requestedFile);
} else {
error404(req, resp);
}
}
} else {
resp.setStatus(HttpServletResponse.SC_FORBIDDEN);
resp.getWriter().println("Stop it!");
writeResponse(resp, SC_FORBIDDEN, "Stop it!");
}
}
private void writeResponse(HttpServletResponse resp, int code, String body) {
try {
resp.setStatus(code);
resp.getWriter().write(body);
} catch (IOException e) {
LOG.error("Couldn't write HTTP response", e);
}
}
@ -77,23 +80,32 @@ public class HlsServlet extends AbstractCtbrecServlet {
resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
}
private void serveSegment(HttpServletRequest req, HttpServletResponse resp, File requestedFile) throws FileNotFoundException, IOException {
serveFile(resp, requestedFile, "application/octet-stream");
private void serveSegment(HttpServletRequest req, HttpServletResponse resp, File requestedFile) throws IOException {
String mimetype = requestedFile.getName().endsWith(".mp4") ? "video/mp4" : "application/octet-stream";
serveFile(req, resp, requestedFile, mimetype);
}
private void servePlaylist(HttpServletRequest req, HttpServletResponse resp, File requestedFile) throws FileNotFoundException, IOException, ParseException, PlaylistException {
serveFile(resp, requestedFile, "application/x-mpegURL");
private void servePlaylist(HttpServletRequest req, HttpServletResponse resp, File requestedFile) throws IOException {
serveFile(req, resp, requestedFile, "application/x-mpegURL");
}
private void serveFile(HttpServletResponse resp, File file, String contentType) throws FileNotFoundException, IOException {
LOG.trace("Serving segment {}", file.getAbsolutePath());
resp.setStatus(200);
resp.setContentLength((int) file.length());
private void serveFile(HttpServletRequest req, HttpServletResponse resp, File file, String contentType) throws IOException {
ByteRange range = getByteRange(req, file);
LOG.info("Serving segment {} range: {}-{}", file.getAbsolutePath(), range.from, range.to);
resp.setStatus(range.set ? SC_PARTIAL_CONTENT : SC_OK);
resp.setContentType(contentType);
try(FileInputStream fin = new FileInputStream(file)) {
try (RandomAccessFile fin = new RandomAccessFile(file, "r")) {
fin.seek(range.from);
byte[] buffer = new byte[1024 * 100];
long bytesLeft = range.to - range.from;
resp.setContentLengthLong(bytesLeft);
if (range.set) {
resp.setHeader("Content-Range", "bytes " + range.from + '-' + range.to + '/' + file.length());
}
int bytesToRead = (int) Math.min(bytesLeft, buffer.length);
int length = -1;
while( (length = fin.read(buffer)) >= 0) {
while ((length = fin.read(buffer, 0, bytesToRead)) >= 0) {
bytesLeft -= length;
resp.getOutputStream().write(buffer, 0, length);
}
}
@ -103,4 +115,28 @@ public class HlsServlet extends AbstractCtbrecServlet {
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
private ByteRange getByteRange(HttpServletRequest req, File file) {
ByteRange range = new ByteRange();
range.to = file.length();
String rangeHeader = req.getHeader("Range");
if (rangeHeader != null) {
range.set = true;
Matcher m = Pattern.compile("bytes=(\\d+)-(\\d+)*").matcher(rangeHeader);
if (m.find()) {
range.from = Long.parseLong(m.group(1));
String to = m.group(2);
if (to != null && !to.trim().isEmpty()) {
range.to = Long.parseLong(to);
}
}
}
return range;
}
class ByteRange {
boolean set = false;
long from = 0;
long to = Long.MAX_VALUE;
}
}