title: Issues with streaming apis on spring mvc framework
body: <p>I am writing a spring boot app which has REST apis (using spring mvc framework) that stream audio/video to HTML5 player on the browser. These apis support range requests for the content.</p>
  
  <p>I have run into an issue where the HTML5 video player complains with error ERR_CONTENT_LENGTH_MISMATCH periodically during streaming. </p>
  
  <p>It seems that bytes received from server do not match bytes advertised by server in Content-Length header.</p>
  
  <p>Please advise what could be the root cause of this. </p>
  
  <p>Things that I have researched so far that could potentially solve the issue but haven't in my case:</p>
  
  <p>No buffering in response.
  No apache in front of tomcat.</p>
  
  <p>Here is my code:</p>
  
  <pre><code>@Api("Player API")
  </code></pre>
  
  <p>@RestController
  public class PlayerController {</p>
  
  <pre><code>@Autowired
  FetchAssetService fetchAssetService;
  
  @ApiOperation("Get video")
  @RequestMapping(value = "player/video/{packageId}/{username}", method = RequestMethod.GET)
  public ResponseEntity&lt;StreamingResponseBody&gt; getProxy(@RequestHeader(value="Range", required=false) String range, @PathVariable Long packageId, @PathVariable String username) throws Exception {
      Optional&lt;Stream&gt; videoAssetMetaData = fetchAssetService.fetchVideoAssetMetaData(packageId);
      if (!videoAssetMetaData.isPresent()) {
          throw new AssetNotFoundException("Video asset not found in MPL for package: "+packageId);
      }
      HttpHeaders httpHeaders = new HttpHeaders();
      HttpStatus status = HttpStatus.OK;
      Optional&lt;AssetRange&gt; optionalAssetRange = AssetRange.create(range,videoAssetMetaData.get().getLength());
      if (optionalAssetRange.isPresent()) {
          if (optionalAssetRange.get().isSatisfiable()) {
              setSuccessRangeHeaders(httpHeaders,optionalAssetRange.get());
              status = HttpStatus.PARTIAL_CONTENT;
          } else {
              setErrorRangeHeaders(httpHeaders,optionalAssetRange.get());
              status = HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE;
              return new ResponseEntity(null,httpHeaders,status);
          }
      }
      setContentHeaders(httpHeaders, “video.mp4");
      try {
          return new ResponseEntity(fetchAssetService.getStreamingResponseBody(packageId,videoAssetMetaData.get(),optionalAssetRange,username),
                  httpHeaders,
                  status);
      } catch (Exception ex) {
          log.error("Exception while video streaming: package={}, user={}, range={}",packageId,username,range,ex);
          throw ex;
      }
  }
  
  private void setContentHeaders(HttpHeaders httpHeaders, String fileName) {
      httpHeaders.add(HttpHeaders.ACCEPT_RANGES,"bytes");
      httpHeaders.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE);
      httpHeaders.add(HttpHeaders.CONTENT_DISPOSITION,"attachment; filename="+ fileName);
  }
  
  private void setSuccessRangeHeaders(HttpHeaders httpHeaders, AssetRange range) {
      httpHeaders.add(HttpHeaders.CONTENT_LENGTH, Long.toString(range.getRangeLength()));
      httpHeaders.add(HttpHeaders.CONTENT_RANGE, String.format("bytes %d-%d/%d", range.getStart(), range.getEnd(), range.getTotalLength()));
  }
  
  private void setErrorRangeHeaders(HttpHeaders httpHeaders, AssetRange range) {
      httpHeaders.add(HttpHeaders.CONTENT_RANGE, String.format("bytes */%d", range.getTotalLength()));
  }
  
  @ExceptionHandler(AssetNotFoundException.class)
  @ResponseStatus(HttpStatus.NOT_FOUND)
  public String handleAppException(AssetNotFoundException ex) {
      return ex.getMessage();
  }
  
  @ExceptionHandler(Exception.class)
  @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
  public String handleAppException(Exception ex) {
      return ex.getMessage();
  }
  </code></pre>
  
  <p>}</p>