package com.backend.hls.proxy.service; import java.io.IOException; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import com.backend.hls.proxy.exception.FetchFailException; import com.backend.hls.proxy.model.SimpleResponse; @Service public class FetchService { private final HttpClient httpClient; public FetchService(HttpClient httpClient) { this.httpClient = httpClient; } /** * Fetch text content from URL * * @throws FetchFailException */ @Cacheable("hlsPlaylistContent") public String fetchTextContent(String url) throws FetchFailException { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(url)) .build(); try { HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() != 200) { throw new FetchFailException( "Failed to fetch content from " + url + ", status: " + response.statusCode(), response); } return response.body(); } catch (IOException | InterruptedException e) { throw new FetchFailException("Failed to fetch content from " + url, e); } } /** * Fetch binary content from URL * * @throws FetchFailException */ @Cacheable("playlistSegmentContent") public SimpleResponse fetchBinaryContent(String url) throws FetchFailException { return fetchBinaryContent(url, null); } /** * Fetch binary content from URL * * @throws FetchFailException */ @Cacheable("playlistSegmentContent") public SimpleResponse fetchBinaryContent(String url, String rangeHeader) throws FetchFailException { HttpRequest.Builder builder = HttpRequest.newBuilder() .uri(URI.create(url)); if (rangeHeader != null && !rangeHeader.isEmpty()) { builder.header("Range", rangeHeader); } try { HttpResponse response = httpClient.send(builder.build(), HttpResponse.BodyHandlers.ofByteArray()); if (response.statusCode() >= 400) { throw new FetchFailException( "Failed to fetch content from " + url + ", status: " + response.statusCode(), response); } return new SimpleResponse(response.body(), response.headers()); } catch (IOException | InterruptedException e) { throw new FetchFailException("Failed to fetch content from " + url, e); } } /** * Fetch content length * * @throws FetchFailException */ public long getContentLength(String url) throws IOException, InterruptedException, FetchFailException { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(url)) .method("HEAD", HttpRequest.BodyPublishers.noBody()) .timeout(Duration.ofSeconds(10)) .build(); HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.discarding()); if (response.statusCode() >= 400) { throw new FetchFailException("HTTP " + response.statusCode(), response); } return response.headers() .firstValueAsLong("Content-Length") .orElse(-1L); } }