有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

http Java实现字节范围服务,而不使用仅使用Java api的servlet

我用Java编写http服务器已经有一段时间了(差不多两年了?)我仍然在处理字节范围请求时遇到问题。我只使用socket的输入和输出流进行原始字节数据传输(即文件下载/上传),使用PrintWriter向连接的客户端发送响应头/字符串(例如HTTP/1.1 200 OK等等)

我不希望使用任何servlet或API(如HTTPURLConnection或其他)。我想做“香草风格”

我能够很好很快地为普通网页提供服务(比如文件浏览、上传和下载、看电影、听音乐、查看pdf文件、文本、图片、gif文件等),所以这不是问题

但是,每当我尝试实现字节范围请求时,服务器都会完美地接收客户机的请求,解析给定的数据,准备文件输入流以便发送,然后当我向客户机发送数据时,客户机总是断开与software caused connection abort: socket write error的连接。(我需要字节范围请求,例如:观看一小时长的视频,然后dang wifi信号消失,您不想从square one重新观看整个视频,恢复“暂停下载”等。)

这确实让我感到困惑,我确实搜索了提供字节范围请求的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上联机。没有ip。org(指向我的服务器),目前我用Accept-Ranges: none而不是Accept-Ranges: bytes禁用了字节请求,但我可以再次打开它来测试它

如果我需要添加更多的代码,请让我知道!谢谢你抽出时间。 或者,如果您希望完整查看我的服务器代码,可以在github上查看:https://github.com/br45entei/JavaWebServer


共 (2) 个答案

  1. # 1 楼答案

    所以我明白了我的问题所在,部分原因是Whome。首先,我没有向客户端发送正确的Content-Length:头,其次,我的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秒超时关闭连接时,网页重置。我还没有在其他浏览器上进行测试,但我认为它应该可以工作

  2. # 2 楼答案

    这个读写循环可以正常工作吗?当然看起来是流线型的?这是我脑子里想不出来的,所以可能会发现一些小的语法错误。我期待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;
    }