forked from j62/ctbrec
Add byte range support for HTTP requests
This commit is contained in:
parent
cb90ff8264
commit
5ce2bd9901
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue