Java 仅使用 Java api 在没有 servlet 的情况下实现字节范围服务
Java Implement byte range serving without servlets using only Java api
我在 Java 中编写一个 http 服务器已经有一段时间了(现在快两年了?)但我仍然无法让字节范围请求正常工作。我只使用套接字的输入和输出流进行原始字节数据传输(即文件 downloads/uploads),以及 PrintWriter
用于发送响应 headers/strings 到连接客户端(例如 HTTP/1.1 200 OK
等等)。
我不想使用任何 servlet 或 api(例如 HTTPURLConnection 或其他)。我想做 "vanilla style".
我可以很好很快速地服务于正常的网页(例如文件浏览、上传和下载、看电影、听音乐、查看pdf文件、文本、图片、gif文件等),所以这不是问题。
但是,每当我尝试实现字节范围请求时,服务器都会完美地接收客户端的请求,解析给定的数据,准备要发送的文件输入流,然后当我将数据发送到客户端时,客户端总是断开与 software caused connection abort: socket write error
的连接。(对于以下情况,我需要字节范围请求:观看一个小时长的视频,然后该死的 wifi 信号消失,你不想从第一个广场重新观看整个视频, 恢复一个 'paused download', 等等)
这真的让我很困惑,我确实搜索了 java 服务字节范围请求的示例,但当我尝试实现它们时,所有这些都失败了。我什至尝试过从头开始并测试它,它产生了相同的结果。以下是与我要完成的任务相关的代码片段:
发送和接收正常的网页(工作正常,这是一个例子):
...
OutputStream outStream = client.getOutputStream();
PrintWriter out = new PrintWriter(new OutputStreamWriter(outStream, StandardCharsets.UTF_8), true);
out.println("HTTP/1.1 200 OK");
out.println("Content-Type: text/html; charset=\"UTF-8\"");
String responseStr = "<!DOCTYPE html><html><head><title>Hello, world!</title></head><body><string>This is a simple example webpage!</string></body></html>";
if(acceptEncoding.contains("gzip") && responseStr.length() > 33) {
out.println("Content-Encoding: gzip");
byte[] r = StringUtil.compressString(responseStr, "UTF-8");
out.println("Content-Length: " + r.length);
out.println("");
if(protocol.equalsIgnoreCase("GET")) {
outStream.write(r);
}
} else {
out.println("Content-Length: " + responseStr.length());
out.println("");
if(request.protocol.equalsIgnoreCase("GET")) {
out.println(responseStr);
}
}
out.flush();
//followed by closing resources, etc.
...
服务字节范围(解析客户端请求数据后):
public static final long copyInputStreamToOutputStream(InputStream input, OutputStream output, long startBytes, long endBytes) throws IOException {
byte[] buffer = new byte[20480];//1024
long count = 0;
int read;
input.skip(startBytes);
long toRead = (endBytes - startBytes) + 1;
while((read = input.read(buffer)) > 0) {
if((toRead -= read) > 0) {
output.write(buffer, 0, read);//Socket error happens on this line mostly
output.flush();
} else {
output.write(buffer, 0, (int) toRead + read);//but also on this line sometimes
output.flush();
break;
}
count += read;
}
return count;
}
对于任何感兴趣的人,此基本代码上的服务器 运行 在 redsandbox.no-ip.org 在线(指向我的服务器),目前我有字节使用 Accept-Ranges: none
而不是 Accept-Ranges: bytes
禁用请求,但我可以再次打开它进行测试。
如果我需要添加更多代码,请告诉我!感谢您的时间。 或者,如果您想查看我服务器的完整代码,可以在 github 上查看:https://github.com/br45entei/JavaWebServer
所以我弄清楚了我的问题所在,部分要感谢 Whome。首先,我没有向客户端发送正确的 Content-Length:
header,其次,我的 endbytes
变量计算不正确。这是工作代码:
public static final long copyInputStreamToOutputStream(InputStream input, OutputStream output, long startBytes, long endBytes, final long contentLength, boolean debug) throws IOException {
byte[] buffer = new byte[20480];//1024
long count = 0;
int read;
long skipped = input.skip(startBytes);//I tested this quite a few times with different files and it does indeed skip the desired amount of bytes.
final long length = (endBytes - startBytes) + 1;
if(debug) {
println("Start bytes: " + startBytes + "; End bytes: " + endBytes + "; Skipped bytes: " + skipped + "; Skipped equals startBytes: " + (skipped == startBytes) + "; Length: " + length + "; Total file size: " + contentLength);
}
long toRead = length;
while((read = input.read(buffer)) != -1) {
count += read;
long bytesLeft = length - count;
if(debug) {
println("read: " + read + "; toRead: " + toRead + "; length: " + length + "; count: " + count + "; bytesLeft: " + bytesLeft);
}
toRead -= read;
if(bytesLeft >= buffer.length) {//Changed the if statement here so that we actually send the last amount of data to the client instead of whatever is leftover in the buffer
output.write(buffer, 0, read);
output.flush();
} else {
output.write(buffer, 0, read);//Send the currently read bytes
output.flush();
bytesLeft = length - count;//recalculate the remaining byte count
read = input.read(buffer, 0, (int) bytesLeft);//read the rest of the required bytes
count += read;
if(debug) {
println("[Last one!]read: " + read + "; toRead: " + toRead + "; length: " + length + "; count: " + count + "; bytesLeft: " + bytesLeft);
}
output.write(buffer, 0, read);//and send them over
output.flush();
break;
}
}
return count;
}
因为在我的例子中 endBytes
变量比它需要的多了一个,我试图通过调整它周围的代码来补偿。但是,我需要简单地从中减去一个。带有 buffer.length
的 if 语句是为了确保将最后一个字节发送到客户端。在我没有它的测试中,客户端(google chrome)挂起并等待剩余的字节,但从未收到它们,然后当 30 秒超时关闭连接时,网页将重置。我还没有用其他浏览器对此进行测试,但我认为它应该可以工作。
这个读写循环能正常工作吗,看起来很流线型?这是我的头脑,所以可能会发现轻微的语法错误。我希望 startIdx+endIdx 都是包容性指标。
public static final long copyInputToOutput(InputStream input, OutputStream output, long startIdx, long endIdx) throws IOException {
final long maxread = 24*1024;
byte[] buffer = new byte[(int)maxread];
input.skip(startIdx);
long written=0;
long remaining = endIdx-startIdx+1;
while(remaining>0) {
int read = input.read(buffer, 0, (int)Math.min(maxread, remaining));
output.write(buffer, 0, read);
remaining -= read;
written += read;
}
output.flush();
return written;
}