Search code examples
javarestspring-bootjettyjetty-8

Issue in producing a Partial Content(206) as output for chunked download of a zip file using rest web services


I have written a rest web service to produce a zip file in chunks using the mime type application_octet_stream using spring boot(2.0.5.RELEASE) but I am unable to get the partial_content(206) as output when i specify the range using the curl command as below but i get a 200(OK) as response i.e. iam not unable to retrieve the file in terms of chunks.

curl http://localhost:8080/resource/getlist -i -H  "Range: bytes=0-100"

PS:The spring boot application is deployed on an external jetty server and iam not using the embedded tomcat and jetty server where the exclusions are done in pom.xml.This is due to various design reasons.

Is there any configuration additions/modifications i need to do on the external jetty server?

The code is as below

@RestController
@RequestMapping("/resource")
public class ListResource {

    /* Logger **/
    private static final Logger _LOG = LoggerFactory.getLogger(ListResource.class);

    @Autowired
    private AppContext appContext;

    private static final String DATE_FORMAT_FOR_ETAG = "yyyy-MM-dd HH:mm:ss'Z'";
    private static final DateFormat eTagDateFormat = new SimpleDateFormat(DATE_FORMAT_FOR_ETAG, Locale.US);
    private static final String ACCEPT_RANGES_BYTES = "bytes";

    @RequestMapping(path = "/getlist", method = RequestMethod.HEAD)
    public ResponseEntity<?> fetchListHead() throws IOException {
        return fetchList(true);
    }

    @RequestMapping(path = "/getlist", method = RequestMethod.GET,produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
    public ResponseEntity<?> fetchListGet() throws IOException {
        return fetchList(false);
    }

    private ResponseEntity<?> fetchList(final boolean justHead) throws IOException {
        File file = null;
        try {
                file = new File("/home/resource/hello.zip");
                byte[] readBytes = Files.readAllBytes(file.toPath());
                ByteArrayResource resource = new ByteArrayResource(readBytes);
                ResponseEntity.BodyBuilder responseBuilder = ResponseEntity.ok()
                              .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + file.getName() + "\"")
                              .header(HttpHeaders.ACCEPT_RANGES, ACCEPT_RANGES_BYTES)
                              .lastModified(new Date().getTime())
                              .eTag(getETag(file))
                              .cacheControl(CacheControl.maxAge(3600, TimeUnit.SECONDS).cachePublic().mustRevalidate())
                              .contentLength(file.length())
                    .contentType(MediaType.parseMediaType(MediaType.APPLICATION_OCTET_STREAM_VALUE));

            return justHead ? responseBuilder.build() : responseBuilder.body(resource);

        } catch (Exception e) {
            _LOG.error("Error in gettingResource:{}", e.getMessage());
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
        }
    }

    private String getETag(File file) {
        String tag = "";
        if (file != null) {
            tag = eTagDateFormat.format(new Date(file.getAbsoluteFile().lastModified()));
        }
        return tag;
    }
}

Solution

  • It is not the role of the Jetty server to handle ranged requests to dynamic content (your REST service).

    The only ranged request support that Jetty provides is ...

    • Incoming request ranges with overlapping ranges are coalesced into sane ranges before being dispatched to the context.
    • Invalid request ranges (negatives for example) result in BadMessageException and a 400 response to the user-agent.
    • Static content served via DefaultServlet supports ranged requests.

    It is up to your dynamic endpoint (your rest API) to read the requested ranges and determine if serving those specific ranges are supported, and then either serving it, rejecting the ranges, or serving the entire content.

    If you feel this is something Jetty should support, feel free to file an enhancement request at https://github.com/eclipse/jetty.project/issues