Java 如何使用Jersey启用可恢复的GET请求?

Java 如何使用Jersey启用可恢复的GET请求?,java,rest,jersey,Java,Rest,Jersey,我正在使用Jersey创建一个RESTful web服务。一些资源是二进制文件,我根据需要从其他地方获取;这样的文件可能很大(数百兆字节) 我希望浏览器能够获取这些资源,所以我有一个@GET注释的方法返回StreamingOutput,如中所示 我有两个问题: StreamingOutput是返回文件的正确方式吗 我应该在服务器端做些什么,使浏览器能够恢复中断的文件传输 只需使用与范围相关的HTTP头,注意w.r.t.缓存。首先,通知您可以通过设置标题继续。其次,检查范围和If范围标头,并发送

我正在使用Jersey创建一个RESTful web服务。一些资源是二进制文件,我根据需要从其他地方获取;这样的文件可能很大(数百兆字节)

我希望浏览器能够获取这些资源,所以我有一个@GET注释的方法返回StreamingOutput,如中所示

我有两个问题:

  • StreamingOutput是返回文件的正确方式吗
  • 我应该在服务器端做些什么,使浏览器能够恢复中断的文件传输

只需使用与范围相关的HTTP头,注意w.r.t.缓存。首先,通知您可以通过设置标题继续。其次,检查范围和If范围标头,并发送相应的响应


请注意,您可能需要构建自己的响应,并手动设置所需的头和结果代码。

出于同样的目的,我构建了一个
ContainerResponseFilter
来拦截具有
范围的头的请求,并相应地损坏响应[1]

这是
ContainerResponseFilter
,它将使用将输出流封装在
RangedOutStream
中(见下文):

这是
范围doutputstream

public class RangedOutputStream extends OutputStream {

    public class Range extends OutputStream {

        private ByteArrayOutputStream outputStream;

        private Integer from;

        private Integer to;

        public Range(Integer from, Integer to) {
            this.outputStream = new ByteArrayOutputStream();
            this.from = from;
            this.to = to;
        }

        public boolean contains(Integer i) {
            if (this.to == null) {
                return (this.from <= i);
            }
            return (this.from <= i && i <= this.to);
        }

        public byte[] getBytes() {
            return this.outputStream.toByteArray();
        }

        public Integer getFrom() {
            return this.from;
        }

        public Integer getTo(Integer ifNull) {
            return this.to == null ? ifNull : this.to;
        }

        @Override
        public void write(int b) throws IOException {
            this.outputStream.write(b);
        }

    }

    private final static char[] MULTIPART_CHARS = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
            .toCharArray();

    private static final String BOUNDARY_LINE_FORMAT = "--%s";

    private static final String CONTENT_TYPE_LINE_FORMAT = "Content-Type: %s";

    private static final String CONTENT_RANGE_FORMAT = "%s %d-%d/%d";

    private static final String CONTENT_RANGE_LINE_FORMAT = "Content-Range: " + CONTENT_RANGE_FORMAT;

    private static final String EMPTY_LINE = "\r\n";

    private OutputStream outputStream;

    private String boundary;

    private String accept;

    private String contentType;

    private boolean multipart;

    private boolean flushed = false;

    private int pos = 0;

    List<Range> ranges;

    MultivaluedMap<String, Object> headers;

    public RangedOutputStream(OutputStream outputStream, String ranges, String contentType, MultivaluedMap<String, Object> headers) {
        this.outputStream = outputStream;
        this.ranges = new ArrayList<>();
        String[] acceptRanges = ranges.split("=");
        this.accept = acceptRanges[0];
        for (String range : acceptRanges[1].split(",")) {
            String[] bounds = range.split("-");
            this.ranges.add(new Range(Integer.valueOf(bounds[0]), bounds.length == 2 ? Integer.valueOf(bounds[1]) : null ));
        }
        this.headers = headers;
        this.contentType = contentType;
        this.multipart = this.ranges.size() > 1;
        this.boundary = this.generateBoundary();
    }

    private String generateBoundary() {
        StringBuilder buffer = new StringBuilder();
        Random rand = new Random();
        int count = rand.nextInt(11) + 30;
        for (int i = 0; i < count; i++) {
            buffer.append(MULTIPART_CHARS[rand.nextInt(MULTIPART_CHARS.length)]);
        }
        return buffer.toString();
    }

    public boolean isMultipart() {
        return this.multipart;
    }

    public String getBoundary() {
        return this.boundary;
    }

    public String getAcceptRanges() {
        return this.accept;
    }

    public String getContentRange(int index) {
        Range range = this.ranges.get(index);
        return String.format(CONTENT_RANGE_LINE_FORMAT, this.accept, range.getFrom(), range.getTo(this.pos), this.pos);
    }

    @Override
    public void write(int b) throws IOException {
        for (Range range : this.ranges) {
            if (range.contains(this.pos)) {
                range.write(b);
            }
        }
        this.pos++;
    }

    @Override
    public void flush() throws IOException {
        if (this.flushed) {
            return;
        }
        if (this.multipart) {
            this.headers.putSingle(HttpHeaders.CONTENT_TYPE, String.format("multipart/byteranges; boundary=%s", this.boundary));
            for (Range range : this.ranges) {
                this.outputStream.write(String.format(BOUNDARY_LINE_FORMAT + EMPTY_LINE, this.boundary).getBytes());
                this.outputStream.write(String.format(CONTENT_TYPE_LINE_FORMAT + EMPTY_LINE, this.contentType).getBytes());
                this.outputStream.write(
                        String.format(CONTENT_RANGE_LINE_FORMAT + EMPTY_LINE, this.accept, range.getFrom(), range.getTo(this.pos), this.pos)
                                .getBytes());
                this.outputStream.write(EMPTY_LINE.getBytes());
                this.outputStream.write(range.getBytes());
                this.outputStream.write(EMPTY_LINE.getBytes());
            }
            this.outputStream.write(String.format(BOUNDARY_LINE_FORMAT, this.boundary + "--").getBytes());
        } else {
            Range range = this.ranges.get(0);
            this.headers.putSingle("Content-Range", String.format(CONTENT_RANGE_FORMAT, this.accept, range.getFrom(), range.getTo(this.pos), this.pos));
            this.outputStream.write(range.getBytes());
        }
        this.flushed = true;
    }

}
公共类RangedOutputStream扩展了OutputStream{
公共类范围扩展了OutputStream{
私有ByteArrayOutputStream输出流;
来自的私有整数;
私有整数到;
公共范围(整数从、整数到){
this.outputStream=newbytearrayoutputstream();
this.from=from;
这个;
}
公共布尔包含(整数i){
if(this.to==null){
归还
public class RangedOutputStream extends OutputStream {

    public class Range extends OutputStream {

        private ByteArrayOutputStream outputStream;

        private Integer from;

        private Integer to;

        public Range(Integer from, Integer to) {
            this.outputStream = new ByteArrayOutputStream();
            this.from = from;
            this.to = to;
        }

        public boolean contains(Integer i) {
            if (this.to == null) {
                return (this.from <= i);
            }
            return (this.from <= i && i <= this.to);
        }

        public byte[] getBytes() {
            return this.outputStream.toByteArray();
        }

        public Integer getFrom() {
            return this.from;
        }

        public Integer getTo(Integer ifNull) {
            return this.to == null ? ifNull : this.to;
        }

        @Override
        public void write(int b) throws IOException {
            this.outputStream.write(b);
        }

    }

    private final static char[] MULTIPART_CHARS = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
            .toCharArray();

    private static final String BOUNDARY_LINE_FORMAT = "--%s";

    private static final String CONTENT_TYPE_LINE_FORMAT = "Content-Type: %s";

    private static final String CONTENT_RANGE_FORMAT = "%s %d-%d/%d";

    private static final String CONTENT_RANGE_LINE_FORMAT = "Content-Range: " + CONTENT_RANGE_FORMAT;

    private static final String EMPTY_LINE = "\r\n";

    private OutputStream outputStream;

    private String boundary;

    private String accept;

    private String contentType;

    private boolean multipart;

    private boolean flushed = false;

    private int pos = 0;

    List<Range> ranges;

    MultivaluedMap<String, Object> headers;

    public RangedOutputStream(OutputStream outputStream, String ranges, String contentType, MultivaluedMap<String, Object> headers) {
        this.outputStream = outputStream;
        this.ranges = new ArrayList<>();
        String[] acceptRanges = ranges.split("=");
        this.accept = acceptRanges[0];
        for (String range : acceptRanges[1].split(",")) {
            String[] bounds = range.split("-");
            this.ranges.add(new Range(Integer.valueOf(bounds[0]), bounds.length == 2 ? Integer.valueOf(bounds[1]) : null ));
        }
        this.headers = headers;
        this.contentType = contentType;
        this.multipart = this.ranges.size() > 1;
        this.boundary = this.generateBoundary();
    }

    private String generateBoundary() {
        StringBuilder buffer = new StringBuilder();
        Random rand = new Random();
        int count = rand.nextInt(11) + 30;
        for (int i = 0; i < count; i++) {
            buffer.append(MULTIPART_CHARS[rand.nextInt(MULTIPART_CHARS.length)]);
        }
        return buffer.toString();
    }

    public boolean isMultipart() {
        return this.multipart;
    }

    public String getBoundary() {
        return this.boundary;
    }

    public String getAcceptRanges() {
        return this.accept;
    }

    public String getContentRange(int index) {
        Range range = this.ranges.get(index);
        return String.format(CONTENT_RANGE_LINE_FORMAT, this.accept, range.getFrom(), range.getTo(this.pos), this.pos);
    }

    @Override
    public void write(int b) throws IOException {
        for (Range range : this.ranges) {
            if (range.contains(this.pos)) {
                range.write(b);
            }
        }
        this.pos++;
    }

    @Override
    public void flush() throws IOException {
        if (this.flushed) {
            return;
        }
        if (this.multipart) {
            this.headers.putSingle(HttpHeaders.CONTENT_TYPE, String.format("multipart/byteranges; boundary=%s", this.boundary));
            for (Range range : this.ranges) {
                this.outputStream.write(String.format(BOUNDARY_LINE_FORMAT + EMPTY_LINE, this.boundary).getBytes());
                this.outputStream.write(String.format(CONTENT_TYPE_LINE_FORMAT + EMPTY_LINE, this.contentType).getBytes());
                this.outputStream.write(
                        String.format(CONTENT_RANGE_LINE_FORMAT + EMPTY_LINE, this.accept, range.getFrom(), range.getTo(this.pos), this.pos)
                                .getBytes());
                this.outputStream.write(EMPTY_LINE.getBytes());
                this.outputStream.write(range.getBytes());
                this.outputStream.write(EMPTY_LINE.getBytes());
            }
            this.outputStream.write(String.format(BOUNDARY_LINE_FORMAT, this.boundary + "--").getBytes());
        } else {
            Range range = this.ranges.get(0);
            this.headers.putSingle("Content-Range", String.format(CONTENT_RANGE_FORMAT, this.accept, range.getFrom(), range.getTo(this.pos), this.pos));
            this.outputStream.write(range.getBytes());
        }
        this.flushed = true;
    }

}