4 Mayıs 2020 Pazartesi

SpringMVC StreamingResponseBody Sınıfı - java.util.stream.Stream Dönmeyi Kolaylaştırır

Giriş
Şu satırı dahil ederiz. StreamingResponseBody aslında bir OutputStream olduğu için tüm metodları destekler
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
Açıklaması şöyle. Servlet thread'ini meşgul etmemeyi sağlar. Controller tarafından döndürülür.
Streaming data is a radical new approach to sending data to web browsers which provides for dramatically faster page load times. Quite often, we need to allow users to download files in web applications. When the data is too large, it becomes quite a challenge to provide a good user experience.

Spring offers support for asynchronous request processing via StreamingResponseBody. In this approach, an application can write data directly to the response OutputStream without holding up the Servlet container thread. There are a few other methods in Spring to handle asynchronous request processing.
write metodu
Örnek
Şöyle yaparız. Controller tarafında StreamingResponseBody  döndürürüz. Service sınıfımız da OutputStream'a asenkron olarak yazar.
@RestController
@RequestMapping("/api")
public class UsersController {
  @Autowired
  private UserService service;

  @GetMapping(value = "/userstream")
  public ResponseEntity<StreamingResponseBody> fetchUsersStream() {

    StreamingResponseBody stream = this::writeTo;
    return new ResponseEntity<>(stream, HttpStatus.OK);
  }

  private void writeTo(OutputStream outputStream) {
    service.writeToOutputStream(outputStream);
  }
}
Service sınıfı şöyledir. Repository sınıfı java.util.stream.Stream nesnesi döner.
@Service
public class UserService {

  @Autowired
  private UsersRepository usersRepository;

  @Transactional(readOnly = true)
  public void writeToOutputStream(final OutputStream outputStream) {
    try (Stream<UsersEntity> usersResultStream = usersRepository
      .findAllByCustomQueryAndStream()) {
      try (ObjectOutputStream oos = new ObjectOutputStream(outputStream)) {

        usersResultStream.forEach(emp -> {
          try {
            oos.write(emp.toString().getBytes());
          } catch (IOException e) {
            e.printStackTrace();
          }
        });
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }
}
Örnek
Elimizde şöyle bir kod olsun. JdbcTemplate kullanarak OutputStream nesnesine yazıyor
public void streamProfilesFromDb(OutputStream outputStream,
Long segmentId, UUID versionId) { DataSource dataSource = Objects.requireNonNull(jdbcTemplate.getDataSource()); try (Connection con = dataSource.getConnection()) { con.setAutoCommit(false); // (!) try (PreparedStatement preparedStatement = con.prepareStatement(SELECT_PROFILES)) { preparedStatement.setLong(1, segmentId); preparedStatement.setObject(2, versionId); preparedStatement.setFetchSize(BATCH_SIZE); writeRowToOutputStream(outputStream, HEADERS); ResultSet resultSet = preparedStatement.executeQuery(); while (resultSet.next()) { List<String> columns = new ArrayList<>(); for (String profileHeader : HEADERS) { columns.add(resultSet.getString(profileHeader)); } writeRowToOutputStream(outputStream, columns); outputStream.flush(); // (!) } } } catch (SQLException | IOException exception) { ... } }
Kullanmak için şöyle yaparız. ResponseEntity.body() metodu
@GetMapping(value = "/stream/{id}")
public ResponseEntity<StreamingResponseBody> streamSegment(@PathVariable("id") 
  Long segmentId) {
  StreamingResponseBody responseBody = out -> {
    streamService.streamProfiles(out, segmentId);
    out.flush();
    out.close();
  };
  HttpHeaders headers = formHeaders(segmentId);
  return ResponseEntity.ok().headers(headers).body(responseBody);
}
Örnek
Şöyle yaparız
@Controller
public class Stream2 {
  @GetMapping(value = "/play_video/{video_id}")
  @ResponseBody
  public ResponseEntity<StreamingResponseBody> stream(
    @PathVariable("video_id") String video_id,
    @RequestHeader(value = "Range", required = false) String rangeHeader) {        
    try {
      StreamingResponseBody responseStream;
      String filePathString = "/opt/videos/" + video_id + ".mp4";
      Path filePath = Paths.get(filePathString);
      Long fileSize = Files.size(filePath);
      byte[] buffer = new byte[1024];      
      final HttpHeaders responseHeaders = new HttpHeaders();

      if (rangeHeader == null) {
        responseHeaders.add("Content-Type", "video/mp4");
        responseHeaders.add("Content-Length", fileSize.toString());
        responseStream = os -> {
          RandomAccessFile file = new RandomAccessFile(filePathString, "r");
          try (file) {
            long pos = 0;
            file.seek(pos);
            while (pos < fileSize) {                            
              file.read(buffer);
              os.write(buffer);
              pos += buffer.length;
            }
            os.flush();
          } catch (Exception e) {}
        };
        return new ResponseEntity<>(responseStream, responseHeaders, HttpStatus.OK);
      }

      String[] ranges = rangeHeader.split("-");
      Long rangeStart = Long.parseLong(ranges[0].substring(6));
      Long rangeEnd;
      if (ranges.length > 1) {
        rangeEnd = Long.parseLong(ranges[1]);
      } else {
        rangeEnd = fileSize - 1;
      }
      if (fileSize < rangeEnd) {
        rangeEnd = fileSize - 1;
      }

      String contentLength = String.valueOf((rangeEnd - rangeStart) + 1);
      responseHeaders.add("Content-Type", "video/mp4");
      responseHeaders.add("Content-Length", contentLength);
      responseHeaders.add("Accept-Ranges", "bytes");
      responseHeaders.add("Content-Range", "bytes" + " " + rangeStart + "-" + rangeEnd + "/" + fileSize);
      final Long _rangeEnd = rangeEnd;
      responseStream = os -> {
        RandomAccessFile file = new RandomAccessFile(filePathString, "r");
        try (file) {
          long pos = rangeStart;
          file.seek(pos);
          while (pos < _rangeEnd) {                        
            file.read(buffer);
            os.write(buffer);
            pos += buffer.length;
          }
            os.flush();
          } catch (Exception e) {}
        };
        return new ResponseEntity<>(responseStream, responseHeaders, HttpStatus.PARTIAL_CONTENT);

    } catch (FileNotFoundException e) {
      return new ResponseEntity<>(HttpStatus.NOT_FOUND);
    } catch (IOException e) {
      return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }
}


Hiç yorum yok:

Yorum Gönder