So implementieren Sie HTTP-Bytebereichsanfragen in Spring MVC

Lesezeit: 18 Minuten

Benutzer-Avatar
gstackoverflow

Ich habe das Problem mit dem Zurückspulen von Videos auf meiner Website.

Ich finde dieses Problem mit HTTP-Headern heraus.

Meine aktuelle Controller-Methode, die Video zurückgibt:

@RequestMapping(method = RequestMethod.GET, value = "/testVideo")
@ResponseBody
public FileSystemResource testVideo(Principal principal) throws IOException {
   return new FileSystemResource(new File("D:\\oceans.mp4"));
}

Wie schreibe ich folgenden Code mit Byte-Range-Unterstützung um?

PS

Ich habe folgendes Beispiel gesehen http://balusc.blogspot.in/2009/02/fileservlet-supporting-resume-and.html

Aber dieser Code sieht schwer für mich aus und ich kann ihn nicht verstehen. Ich hoffe, dass mvc im Frühjahr viel einfacher existiert.

Benutzer-Avatar
Leandro Carracedo

Die Anfrage zur Unterstützung des HTTP-Bytebereichs war zum Zeitpunkt dieser Antwort offen, wird jedoch in Spring 4.2.RC1 behoben. Überprüfen Sie Jira SPR-10805 oder die PR hier.

Aber basierend auf dem Code, den Sie in Ihrer Frage verlinkt haben, hat Davin Kevin eine erstellt Lösung das könnte für deine Anfrage funktionieren.

Code für MultipartFileSender:

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.FileTime;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Created by kevin on 10/02/15.
 * See full code here : https://github.com/davinkevin/Podcast-Server/blob/d927d9b8cb9ea1268af74316cd20b7192ca92da7/src/main/java/lan/dk/podcastserver/utils/multipart/MultipartFileSender.java
 */
public class MultipartFileSender {

    protected final Logger logger = LoggerFactory.getLogger(this.getClass());

    private static final int DEFAULT_BUFFER_SIZE = 20480; // ..bytes = 20KB.
    private static final long DEFAULT_EXPIRE_TIME = 604800000L; // ..ms = 1 week.
    private static final String MULTIPART_BOUNDARY = "MULTIPART_BYTERANGES";

    Path filepath;
    HttpServletRequest request;
    HttpServletResponse response;

    public MultipartFileSender() {
    }

    public static MultipartFileSender fromPath(Path path) {
        return new MultipartFileSender().setFilepath(path);
    }

    public static MultipartFileSender fromFile(File file) {
        return new MultipartFileSender().setFilepath(file.toPath());
    }

    public static MultipartFileSender fromURIString(String uri) {
        return new MultipartFileSender().setFilepath(Paths.get(uri));
    }

    //** internal setter **//
    private MultipartFileSender setFilepath(Path filepath) {
        this.filepath = filepath;
        return this;
    }

    public MultipartFileSender with(HttpServletRequest httpRequest) {
        request = httpRequest;
        return this;
    }

    public MultipartFileSender with(HttpServletResponse httpResponse) {
        response = httpResponse;
        return this;
    }

    public void serveResource() throws Exception {
        if (response == null || request == null) {
            return;
        }

        if (!Files.exists(filepath)) {
            logger.error("File doesn't exist at URI : {}", filepath.toAbsolutePath().toString());
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        Long length = Files.size(filepath);
        String fileName = filepath.getFileName().toString();
        FileTime lastModifiedObj = Files.getLastModifiedTime(filepath);

        if (StringUtils.isEmpty(fileName) || lastModifiedObj == null) {
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            return;
        }
        long lastModified = LocalDateTime.ofInstant(lastModifiedObj.toInstant(), ZoneId.of(ZoneOffset.systemDefault().getId())).toEpochSecond(ZoneOffset.UTC);
        String contentType = "video/mp4";

        // Validate request headers for caching ---------------------------------------------------

        // If-None-Match header should contain "*" or ETag. If so, then return 304.
        String ifNoneMatch = request.getHeader("If-None-Match");
        if (ifNoneMatch != null && HttpUtils.matches(ifNoneMatch, fileName)) {
            response.setHeader("ETag", fileName); // Required in 304.
            response.sendError(HttpServletResponse.SC_NOT_MODIFIED);
            return;
        }

        // If-Modified-Since header should be greater than LastModified. If so, then return 304.
        // This header is ignored if any If-None-Match header is specified.
        long ifModifiedSince = request.getDateHeader("If-Modified-Since");
        if (ifNoneMatch == null && ifModifiedSince != -1 && ifModifiedSince + 1000 > lastModified) {
            response.setHeader("ETag", fileName); // Required in 304.
            response.sendError(HttpServletResponse.SC_NOT_MODIFIED);
            return;
        }

        // Validate request headers for resume ----------------------------------------------------

        // If-Match header should contain "*" or ETag. If not, then return 412.
        String ifMatch = request.getHeader("If-Match");
        if (ifMatch != null && !HttpUtils.matches(ifMatch, fileName)) {
            response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
            return;
        }

        // If-Unmodified-Since header should be greater than LastModified. If not, then return 412.
        long ifUnmodifiedSince = request.getDateHeader("If-Unmodified-Since");
        if (ifUnmodifiedSince != -1 && ifUnmodifiedSince + 1000 <= lastModified) {
            response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
            return;
        }

        // Validate and process range -------------------------------------------------------------

        // Prepare some variables. The full Range represents the complete file.
        Range full = new Range(0, length - 1, length);
        List<Range> ranges = new ArrayList<>();

        // Validate and process Range and If-Range headers.
        String range = request.getHeader("Range");
        if (range != null) {

            // Range header should match format "bytes=n-n,n-n,n-n...". If not, then return 416.
            if (!range.matches("^bytes=\\d*-\\d*(,\\d*-\\d*)*$")) {
                response.setHeader("Content-Range", "bytes */" + length); // Required in 416.
                response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
                return;
            }

            String ifRange = request.getHeader("If-Range");
            if (ifRange != null && !ifRange.equals(fileName)) {
                try {
                    long ifRangeTime = request.getDateHeader("If-Range"); // Throws IAE if invalid.
                    if (ifRangeTime != -1) {
                        ranges.add(full);
                    }
                } catch (IllegalArgumentException ignore) {
                    ranges.add(full);
                }
            }

            // If any valid If-Range header, then process each part of byte range.
            if (ranges.isEmpty()) {
                for (String part : range.substring(6).split(",")) {
                    // Assuming a file with length of 100, the following examples returns bytes at:
                    // 50-80 (50 to 80), 40- (40 to length=100), -20 (length-20=80 to length=100).
                    long start = Range.sublong(part, 0, part.indexOf("-"));
                    long end = Range.sublong(part, part.indexOf("-") + 1, part.length());

                    if (start == -1) {
                        start = length - end;
                        end = length - 1;
                    } else if (end == -1 || end > length - 1) {
                        end = length - 1;
                    }

                    // Check if Range is syntactically valid. If not, then return 416.
                    if (start > end) {
                        response.setHeader("Content-Range", "bytes */" + length); // Required in 416.
                        response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
                        return;
                    }

                    // Add range.                    
                    ranges.add(new Range(start, end, length));
                }
            }
        }

        // Prepare and initialize response --------------------------------------------------------

        // Get content type by file name and set content disposition.
        String disposition = "inline";

        // If content type is unknown, then set the default value.
        // For all content types, see: http://www.w3schools.com/media/media_mimeref.asp
        // To add new content types, add new mime-mapping entry in web.xml.
        if (contentType == null) {
            contentType = "application/octet-stream";
        } else if (!contentType.startsWith("image")) {
            // Else, expect for images, determine content disposition. If content type is supported by
            // the browser, then set to inline, else attachment which will pop a 'save as' dialogue.
            String accept = request.getHeader("Accept");
            disposition = accept != null && HttpUtils.accepts(accept, contentType) ? "inline" : "attachment";
        }
        logger.debug("Content-Type : {}", contentType);
        // Initialize response.
        response.reset();
        response.setBufferSize(DEFAULT_BUFFER_SIZE);
        response.setHeader("Content-Type", contentType);
        response.setHeader("Content-Disposition", disposition + ";filename=\"" + fileName + "\"");
        logger.debug("Content-Disposition : {}", disposition);
        response.setHeader("Accept-Ranges", "bytes");
        response.setHeader("ETag", fileName);
        response.setDateHeader("Last-Modified", lastModified);
        response.setDateHeader("Expires", System.currentTimeMillis() + DEFAULT_EXPIRE_TIME);

        // Send requested file (part(s)) to client ------------------------------------------------

        // Prepare streams.
        try (InputStream input = new BufferedInputStream(Files.newInputStream(filepath));
             OutputStream output = response.getOutputStream()) {

            if (ranges.isEmpty() || ranges.get(0) == full) {

                // Return full file.
                logger.info("Return full file");
                response.setContentType(contentType);
                response.setHeader("Content-Range", "bytes " + full.start + "-" + full.end + "https://stackoverflow.com/" + full.total);
                response.setHeader("Content-Length", String.valueOf(full.length));
                Range.copy(input, output, length, full.start, full.length);

            } else if (ranges.size() == 1) {

                // Return single part of file.
                Range r = ranges.get(0);
                logger.info("Return 1 part of file : from ({}) to ({})", r.start, r.end);
                response.setContentType(contentType);
                response.setHeader("Content-Range", "bytes " + r.start + "-" + r.end + "https://stackoverflow.com/" + r.total);
                response.setHeader("Content-Length", String.valueOf(r.length));
                response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); // 206.

                // Copy single part range.
                Range.copy(input, output, length, r.start, r.length);

            } else {

                // Return multiple parts of file.
                response.setContentType("multipart/byteranges; boundary=" + MULTIPART_BOUNDARY);
                response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); // 206.

                // Cast back to ServletOutputStream to get the easy println methods.
                ServletOutputStream sos = (ServletOutputStream) output;

                // Copy multi part range.
                for (Range r : ranges) {
                    logger.info("Return multi part of file : from ({}) to ({})", r.start, r.end);
                    // Add multipart boundary and header fields for every range.
                    sos.println();
                    sos.println("--" + MULTIPART_BOUNDARY);
                    sos.println("Content-Type: " + contentType);
                    sos.println("Content-Range: bytes " + r.start + "-" + r.end + "https://stackoverflow.com/" + r.total);

                    // Copy single part range of multi part range.
                    Range.copy(input, output, length, r.start, r.length);
                }

                // End with multipart boundary.
                sos.println();
                sos.println("--" + MULTIPART_BOUNDARY + "--");
            }
        }

    }

    private static class Range {
        long start;
        long end;
        long length;
        long total;

        /**
         * Construct a byte range.
         * @param start Start of the byte range.
         * @param end End of the byte range.
         * @param total Total length of the byte source.
         */
        public Range(long start, long end, long total) {
            this.start = start;
            this.end = end;
            this.length = end - start + 1;
            this.total = total;
        }

        public static long sublong(String value, int beginIndex, int endIndex) {
            String substring = value.substring(beginIndex, endIndex);
            return (substring.length() > 0) ? Long.parseLong(substring) : -1;
        }

        private static void copy(InputStream input, OutputStream output, long inputSize, long start, long length) throws IOException {
            byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
            int read;

            if (inputSize == length) {
                // Write full range.
                while ((read = input.read(buffer)) > 0) {
                    output.write(buffer, 0, read);
                    output.flush();
                }
            } else {
                input.skip(start);
                long toRead = length;

                while ((read = input.read(buffer)) > 0) {
                    if ((toRead -= read) > 0) {
                        output.write(buffer, 0, read);
                        output.flush();
                    } else {
                        output.write(buffer, 0, (int) toRead + read);
                        output.flush();
                        break;
                    }
                }
            }
        }
    }
    private static class HttpUtils {

        /**
         * Returns true if the given accept header accepts the given value.
         * @param acceptHeader The accept header.
         * @param toAccept The value to be accepted.
         * @return True if the given accept header accepts the given value.
         */
        public static boolean accepts(String acceptHeader, String toAccept) {
            String[] acceptValues = acceptHeader.split("\\s*(,|;)\\s*");
            Arrays.sort(acceptValues);

            return Arrays.binarySearch(acceptValues, toAccept) > -1
                    || Arrays.binarySearch(acceptValues, toAccept.replaceAll("/.*$", "/*")) > -1
                    || Arrays.binarySearch(acceptValues, "*/*") > -1;
        }

        /**
         * Returns true if the given match header matches the given value.
         * @param matchHeader The match header.
         * @param toMatch The value to be matched.
         * @return True if the given match header matches the given value.
         */
        public static boolean matches(String matchHeader, String toMatch) {
            String[] matchValues = matchHeader.split("\\s*,\\s*");
            Arrays.sort(matchValues);
            return Arrays.binarySearch(matchValues, toMatch) > -1
                    || Arrays.binarySearch(matchValues, "*") > -1;
        }
    }
}

Verwendung im Controller:

MultipartFileSender.fromPath(pathtofile)
                .with(httprequest)
                .with(httpresponse)
                .serveResource();

  • Ich kann jat mit folgender Klasse nicht finden: lan.dk.podcastserver.utils.MimeTypeUtils

    – gstackoverflow

    12. Februar 2015 um 18:16 Uhr

  • Dieser Import wurde entfernt und geändert, um den Mimetyp auf “video/mp4” zu fixieren, aber das gesamte Projekt wird auf github gepostet: github.com/davinkevin/Podcast-Server

    – Leandro Carracedo

    12. Februar 2015 um 18:21 Uhr

  • Jetzt ist es SO Stil. Frage – Antwort ohne redundanten Kontext. Ich bin sicher, dass es für zukünftige Googler nützlich sein wird.

    – gstackoverflow

    12. Februar 2015 um 20:49 Uhr


  • Gibt es eine Möglichkeit, die Standardimplementierung des statischen Ressourcenauflösers von Spring (ältere Version als 4.2RC1) mit einem Controller zu ändern, der den HTTP-Bereich unterstützen kann, um den Download fortzusetzen?

    – Radu Toader

    6. August 2015 um 14:14 Uhr


  • Dies hat einen Fehler im Multipart-Zweig. Wenn Sie den Stream jedes Mal um die volle Menge überspringen, werden die erwarteten Bytes nicht gelesen, es sei denn, der Stream wird für jeden Lesevorgang markiert / zurückgesetzt. Die bessere Alternative ist (natürlich) den Offset entsprechend der zuletzt gelesenen Position anzupassen.

    – RutledgePaulV

    29. Dezember 2016 um 7:28 Uhr


hier ist 2020.

Teilbereiche sind standardmäßig in Spring implementiert (ich verwende Spring Boot 2.2.4). Mein Controller-Code sieht ähnlich aus wie der unten stehende und funktioniert gut mit Range-Headern.

return ResponseEntity.ok().body(new FileSystemResource(file))

Notizen (vgl Der allmächtige Quellcode für Details):

  • Spring erfordert, dass die Antwort auf 200 gesetzt wird, und ersetzt sie intern (wenn Bereichsheader in der Anfrage vorhanden sind) durch 206 Partial Content.
  • Spring erfordert auch, dass die Antwort eine Ressourceninstanz ist außer InputStreamResource, für die es fehlschlägt. Für Dateien scheint FileSystemResource die beste Wahl zu sein.

  • Das ist heute die beste Antwort. Kein benutzerdefinierter Code erforderlich.

    – A. Mort

    25. September 2020 um 15:34 Uhr

  • Mein Code sieht aus wie URL resource = getClass().getClassLoader().getResource(“nbch_intro.mp4”); return ResponseEntity.ok().body(new FileSystemResource(resource.getFile())); In diesem Fall gibt der Server JSON zurück. Wenn ich .header (“Content-Type”, “video/mp4”) hinzufüge, erhalte ich eine Ausnahme: Kein Konverter für [class org.springframework.core.io.FileSystemResource] mit voreingestelltem Content-Type ‘video/mp4’…

    – Wurm359

    19. Januar 2021 um 19:45 Uhr


Benutzer-Avatar
Kevin David

Wenn Sie vermeiden möchten MimeTypeUtilskönnen Sie verwenden Files.probeContentType(Path path). Dabei wird die Standardimplementierung des Systems verwendet.

Je nachdem, was Sie erreichen möchten, kann es sinnvoll sein, die Anfrage per dem Standard-Servlet des Containers zuzuordnen.

Dh während der Initialisierung:

ServletContext container = ...

// Map *.mp4 to the default servlet to support range requests

ServletRegistration servletRegistration = container.getServletRegistration("default");
servletRegistration.addMapping("*.mp4");

Die Antwort von Leandro hat bei mir perfekt funktioniert. Ich habe den InputStream nur mit einem RandomAccessFile geändert, es funktioniert besser, auf einen zufälligen Punkt in einer Datei zuzugreifen.

Hier ist die Klasse MultipartFileSender

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.FileTime;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class MultipartFileSender {

    protected final Logger logger = LoggerFactory.getLogger(this.getClass());

    private static final int DEFAULT_BUFFER_SIZE = 20480; // ..bytes = 20KB.
    private static final long DEFAULT_EXPIRE_TIME = 604800000L; // ..ms = 1 week.
    private static final String MULTIPART_BOUNDARY = "MULTIPART_BYTERANGES";

    Path filepath;
    HttpServletRequest request;
    HttpServletResponse response;

    public MultipartFileSender() {
    }

    public static MultipartFileSender fromPath(Path path) {
        return new MultipartFileSender().setFilepath(path);
    }

    public static MultipartFileSender fromFile(File file) {
        return new MultipartFileSender().setFilepath(file.toPath());
    }

    public static MultipartFileSender fromURIString(String uri) {
        return new MultipartFileSender().setFilepath(Paths.get(uri));
    }

    //** internal setter **//
    private MultipartFileSender setFilepath(Path filepath) {
        this.filepath = filepath;
        return this;
    }

    public MultipartFileSender with(HttpServletRequest httpRequest) {
        request = httpRequest;
        return this;
    }

    public MultipartFileSender with(HttpServletResponse httpResponse) {
        response = httpResponse;
        return this;
    }

    public void serveResource() throws Exception {
        if (response == null || request == null) {
            return;
        }

        if (!Files.exists(filepath)) {
            logger.error("File doesn't exist at URI : {}", filepath.toAbsolutePath().toString());
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        Long length = Files.size(filepath);
        String fileName = filepath.getFileName().toString();
        FileTime lastModifiedObj = Files.getLastModifiedTime(filepath);

        if (StringUtils.isEmpty(fileName) || lastModifiedObj == null) {
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            return;
        }
    long lastModified = LocalDateTime.ofInstant(lastModifiedObj.toInstant(), ZoneId.of(ZoneOffset.systemDefault().getId())).toEpochSecond(ZoneOffset.UTC);
    String contentType = "video/mp4";

    // Validate request headers for caching ---------------------------------------------------

    // If-None-Match header should contain "*" or ETag. If so, then return 304.
    String ifNoneMatch = request.getHeader("If-None-Match");
    if (ifNoneMatch != null && HttpUtils.matches(ifNoneMatch, fileName)) {
        response.setHeader("ETag", fileName); // Required in 304.
        response.sendError(HttpServletResponse.SC_NOT_MODIFIED);
        return;
    }

    // If-Modified-Since header should be greater than LastModified. If so, then return 304.
    // This header is ignored if any If-None-Match header is specified.
    long ifModifiedSince = request.getDateHeader("If-Modified-Since");
    if (ifNoneMatch == null && ifModifiedSince != -1 && ifModifiedSince + 1000 > lastModified) {
        response.setHeader("ETag", fileName); // Required in 304.
        response.sendError(HttpServletResponse.SC_NOT_MODIFIED);
        return;
    }

    // Validate request headers for resume ----------------------------------------------------

    // If-Match header should contain "*" or ETag. If not, then return 412.
    String ifMatch = request.getHeader("If-Match");
    if (ifMatch != null && !HttpUtils.matches(ifMatch, fileName)) {
        response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
        return;
    }

    // If-Unmodified-Since header should be greater than LastModified. If not, then return 412.
    long ifUnmodifiedSince = request.getDateHeader("If-Unmodified-Since");
    if (ifUnmodifiedSince != -1 && ifUnmodifiedSince + 1000 <= lastModified) {
        response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
        return;
    }

    // Validate and process range -------------------------------------------------------------

    // Prepare some variables. The full Range represents the complete file.
    Range full = new Range(0, length - 1, length);
    List<Range> ranges = new ArrayList<>();

    // Validate and process Range and If-Range headers.
    String range = request.getHeader("Range");
    if (range != null) {

        // Range header should match format "bytes=n-n,n-n,n-n...". If not, then return 416.
        if (!range.matches("^bytes=\\d*-\\d*(,\\d*-\\d*)*$")) {
            response.setHeader("Content-Range", "bytes */" + length); // Required in 416.
            response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
            return;
        }

        String ifRange = request.getHeader("If-Range");
        if (ifRange != null && !ifRange.equals(fileName)) {
            try {
                long ifRangeTime = request.getDateHeader("If-Range"); // Throws IAE if invalid.
                if (ifRangeTime != -1) {
                    ranges.add(full);
                }
            } catch (IllegalArgumentException ignore) {
                ranges.add(full);
            }
        }

        // If any valid If-Range header, then process each part of byte range.
        if (ranges.isEmpty()) {
            for (String part : range.substring(6).split(",")) {
                // Assuming a file with length of 100, the following examples returns bytes at:
                // 50-80 (50 to 80), 40- (40 to length=100), -20 (length-20=80 to length=100).
                long start = Range.sublong(part, 0, part.indexOf("-"));
                long end = Range.sublong(part, part.indexOf("-") + 1, part.length());

                if (start == -1) {
                    start = length - end;
                    end = length - 1;
                } else if (end == -1 || end > length - 1) {
                    end = length - 1;
                }

                // Check if Range is syntactically valid. If not, then return 416.
                if (start > end) {
                    response.setHeader("Content-Range", "bytes */" + length); // Required in 416.
                    response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
                    return;
                }

                // Add range.                    
                ranges.add(new Range(start, end, length));
            }
        }
    }

    // Prepare and initialize response --------------------------------------------------------

    // Get content type by file name and set content disposition.
    String disposition = "inline";

    // If content type is unknown, then set the default value.
    // For all content types, see: http://www.w3schools.com/media/media_mimeref.asp
    // To add new content types, add new mime-mapping entry in web.xml.
    if (contentType == null) {
        contentType = "application/octet-stream";
    } else if (!contentType.startsWith("image")) {
        // Else, expect for images, determine content disposition. If content type is supported by
        // the browser, then set to inline, else attachment which will pop a 'save as' dialogue.
        String accept = request.getHeader("Accept");
        disposition = accept != null && HttpUtils.accepts(accept, contentType) ? "inline" : "attachment";
    }
    logger.debug("Content-Type : {}", contentType);
    // Initialize response.
    response.reset();
    response.setBufferSize(DEFAULT_BUFFER_SIZE);
    response.setHeader("Content-Type", contentType);
    response.setHeader("Content-Disposition", disposition + ";filename=\"" + fileName + "\"");
    logger.debug("Content-Disposition : {}", disposition);
    response.setHeader("Accept-Ranges", "bytes");
    response.setHeader("ETag", fileName);
    response.setDateHeader("Last-Modified", lastModified);
    response.setDateHeader("Expires", System.currentTimeMillis() + DEFAULT_EXPIRE_TIME);

    // Send requested file (part(s)) to client ------------------------------------------------

    // Prepare streams.
    try (RandomAccessFile input = new RandomAccessFile(filepath.toFile(), "r");
         OutputStream output = response.getOutputStream()) {

        if (ranges.isEmpty() || ranges.get(0) == full) {
            // Return full file.
            logger.info("Return full file");
            response.setContentType(contentType);
            response.setHeader("Content-Range", "bytes " + full.start + "-" + full.end + "https://stackoverflow.com/" + full.total);
            response.setHeader("Content-Length", String.valueOf(full.length));
            Range.copy(input, output, length, full.start, full.length);

        } else if (ranges.size() == 1) {

            // Return single part of file.
            Range r = ranges.get(0);
            logger.info("Return 1 part of file : from ({}) to ({})", r.start, r.end);
            response.setContentType(contentType);
            response.setHeader("Content-Range", "bytes " + r.start + "-" + r.end + "https://stackoverflow.com/" + r.total);
            response.setHeader("Content-Length", String.valueOf(r.length));
            response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); // 206.
            // Copy single part range.
            Range.copy(input, output, length, r.start, r.length);

        } else {

            // Return multiple parts of file.
            response.setContentType("multipart/byteranges; boundary=" + MULTIPART_BOUNDARY);
            response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); // 206.

            // Cast back to ServletOutputStream to get the easy println methods.
            ServletOutputStream sos = (ServletOutputStream) output;

            // Copy multi part range.
            for (Range r : ranges) {
                logger.info("Return multi part of file : from ({}) to ({})", r.start, r.end);
                // Add multipart boundary and header fields for every range.
                sos.println();
                sos.println("--" + MULTIPART_BOUNDARY);
                sos.println("Content-Type: " + contentType);
                sos.println("Content-Range: bytes " + r.start + "-" + r.end + "https://stackoverflow.com/" + r.total);
                // Copy single part range of multi part range.
                Range.copy(input, output, length, r.start, r.length);
            }

            // End with multipart boundary.
            sos.println();
            sos.println("--" + MULTIPART_BOUNDARY + "--");
        }
    }

}

private static class Range {
    long start;
    long end;
    long length;
    long total;

    /**
     * Construct a byte range.
     * @param start Start of the byte range.
     * @param end End of the byte range.
     * @param total Total length of the byte source.
     */
    public Range(long start, long end, long total) {
        this.start = start;
        this.end = end;
        this.length = end - start + 1;
        this.total = total;
    }

    public static long sublong(String value, int beginIndex, int endIndex) {
        String substring = value.substring(beginIndex, endIndex);
        return (substring.length() > 0) ? Long.parseLong(substring) : -1;
    }

    private static void copy(RandomAccessFile input, OutputStream output, long inputSize, long start, long length) throws IOException {
        byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
        int read;

        if (inputSize == length) {
            // Write full range.
            while ((read = input.read(buffer)) > 0) {
                output.write(buffer, 0, read);
                output.flush();
            }
        } else {
            input.seek(start);
            long toRead = length;

            while ((read = input.read(buffer)) > 0) {
                if ((toRead -= read) > 0) {
                    output.write(buffer, 0, read);
                    output.flush();
                } else {
                    output.write(buffer, 0, (int) toRead + read);
                    output.flush();
                    break;
                }
            }
        }
    }
    }
    private static class HttpUtils {

    /**
     * Returns true if the given accept header accepts the given value.
     * @param acceptHeader The accept header.
     * @param toAccept The value to be accepted.
     * @return True if the given accept header accepts the given value.
     */
        public static boolean accepts(String acceptHeader, String toAccept) {
            String[] acceptValues = acceptHeader.split("\\s*(,|;)\\s*");
            Arrays.sort(acceptValues);

            return Arrays.binarySearch(acceptValues, toAccept) > -1
                || Arrays.binarySearch(acceptValues, toAccept.replaceAll("/.*$", "/*")) > -1
                || Arrays.binarySearch(acceptValues, "*/*") > -1;
        }

    /**
     * Returns true if the given match header matches the given value.
     * @param matchHeader The match header.
     * @param toMatch The value to be matched.
     * @return True if the given match header matches the given value.
     */
        public static boolean matches(String matchHeader, String toMatch) {
            String[] matchValues = matchHeader.split("\\s*,\\s*");
            Arrays.sort(matchValues);
            return Arrays.binarySearch(matchValues, toMatch) > -1
                || Arrays.binarySearch(matchValues, "*") > -1;
        }
    }
}

Benutzer-Avatar
Fadeef Saleh

woww, die Antwort von Leandro so genial. Erstellen Sie einfach eine Servicedatei, folgen Sie ihm und verwenden Sie sie im Controller. hier mein nach folgen seinen richtlinien

@RequestMapping(value = [(SKConstant.URL_PROJECT_API + "/video/{token}")], method = arrayOf(RequestMethod.GET)
fun getVideo(@PathVariable("token") token: String, response: HttpServletResponse, request: HttpServletRequest): Any?{
         val file = baseService.getByToken(token)
         if(file != null) {
           return MultipartFileSender.fromFile(File(fileService.getUploadFileRealPath(file.uniqueName!!)))
                              .with(request)
                              .with(response)
                              .serveResource()
 }

Benutzer-Avatar
gstackoverflow

Probieren Sie diesen einfachen Code aus.

@GET
@Produces({"application/octet-stream"})
public Response getFile() {
  File file = ... 
  return Response.ok(file, MediaType.APPLICATION_OCTET_STREAM)
      .header("Content-Disposition", "attachment; filename=\"" + file.getName() + "\"" ) .build();
}

Aktualisieren :

Es umschließt die RESTful-Implementierung von Spring Jersey jersey.java.net mit Spring MVC.

Verwenden Sie für reines Spring-mvc den folgenden Code.

@RequestMapping("/video") public ResponseEntity<byte[]> getvideo() throws IOException { 
    InputStream in = ... 
    final HttpHeaders headers = new HttpHeaders(); 
    headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); 
    return new ResponseEntity<byte[]>IOUtils.toByteArray(in),headers,HttpStatus.CREATED); 
}

IOUtils stammt von Apache Common Jar

  • Konvertieren Sie niemals InputStream zu byte[] in diesem Zusammenhang. Sie werden ernsthafte Gedächtnisprobleme haben.

    – bekce

    7. März 2016 um 6:55 Uhr

  • Unterstützt keinen HTTP-Byte-Bereich

    – Kevin Davis

    13. Juli 2016 um 5:16 Uhr

1016780cookie-checkSo implementieren Sie HTTP-Bytebereichsanfragen in Spring MVC

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy