Search in sources :

Example 1 with HttpRange

use of org.springframework.http.HttpRange in project spring-framework by spring-projects.

the class ResourceHttpMessageWriter method write.

// Server-side only: single Resource or sub-regions...
@Override
public Mono<Void> write(Publisher<? extends Resource> inputStream, @Nullable ResolvableType actualType, ResolvableType elementType, @Nullable MediaType mediaType, ServerHttpRequest request, ServerHttpResponse response, Map<String, Object> hints) {
    HttpHeaders headers = response.getHeaders();
    headers.set(HttpHeaders.ACCEPT_RANGES, "bytes");
    List<HttpRange> ranges;
    try {
        ranges = request.getHeaders().getRange();
    } catch (IllegalArgumentException ex) {
        response.setStatusCode(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE);
        return response.setComplete();
    }
    return Mono.from(inputStream).flatMap(resource -> {
        if (ranges.isEmpty()) {
            return writeResource(resource, elementType, mediaType, response, hints);
        }
        response.setStatusCode(HttpStatus.PARTIAL_CONTENT);
        List<ResourceRegion> regions = HttpRange.toResourceRegions(ranges, resource);
        MediaType resourceMediaType = getResourceMediaType(mediaType, resource, hints);
        if (regions.size() == 1) {
            ResourceRegion region = regions.get(0);
            headers.setContentType(resourceMediaType);
            long contentLength = lengthOf(resource);
            if (contentLength != -1) {
                long start = region.getPosition();
                long end = start + region.getCount() - 1;
                end = Math.min(end, contentLength - 1);
                headers.add("Content-Range", "bytes " + start + '-' + end + '/' + contentLength);
                headers.setContentLength(end - start + 1);
            }
            return writeSingleRegion(region, response, hints);
        } else {
            String boundary = MimeTypeUtils.generateMultipartBoundaryString();
            MediaType multipartType = MediaType.parseMediaType("multipart/byteranges;boundary=" + boundary);
            headers.setContentType(multipartType);
            Map<String, Object> allHints = Hints.merge(hints, ResourceRegionEncoder.BOUNDARY_STRING_HINT, boundary);
            return encodeAndWriteRegions(Flux.fromIterable(regions), resourceMediaType, response, allHints);
        }
    });
}
Also used : HttpHeaders(org.springframework.http.HttpHeaders) ResourceRegion(org.springframework.core.io.support.ResourceRegion) MediaType(org.springframework.http.MediaType) HttpRange(org.springframework.http.HttpRange)

Example 2 with HttpRange

use of org.springframework.http.HttpRange in project spring-framework by spring-projects.

the class DefaultClientResponseTests method header.

@Test
public void header() {
    long contentLength = 42L;
    httpHeaders.setContentLength(contentLength);
    MediaType contentType = MediaType.TEXT_PLAIN;
    httpHeaders.setContentType(contentType);
    InetSocketAddress host = InetSocketAddress.createUnresolved("localhost", 80);
    httpHeaders.setHost(host);
    List<HttpRange> range = Collections.singletonList(HttpRange.createByteRange(0, 42));
    httpHeaders.setRange(range);
    given(mockResponse.getHeaders()).willReturn(httpHeaders);
    ClientResponse.Headers headers = defaultClientResponse.headers();
    assertThat(headers.contentLength()).isEqualTo(OptionalLong.of(contentLength));
    assertThat(headers.contentType()).isEqualTo(Optional.of(contentType));
    assertThat(headers.asHttpHeaders()).isEqualTo(httpHeaders);
}
Also used : InetSocketAddress(java.net.InetSocketAddress) MediaType(org.springframework.http.MediaType) HttpRange(org.springframework.http.HttpRange) Test(org.junit.jupiter.api.Test)

Example 3 with HttpRange

use of org.springframework.http.HttpRange in project spring-framework by spring-projects.

the class ResourceHttpRequestHandler method handleRequest.

/**
	 * Processes a resource request.
	 * <p>Checks for the existence of the requested resource in the configured list of locations.
	 * If the resource does not exist, a {@code 404} response will be returned to the client.
	 * If the resource exists, the request will be checked for the presence of the
	 * {@code Last-Modified} header, and its value will be compared against the last-modified
	 * timestamp of the given resource, returning a {@code 304} status code if the
	 * {@code Last-Modified} value  is greater. If the resource is newer than the
	 * {@code Last-Modified} value, or the header is not present, the content resource
	 * of the resource will be written to the response with caching headers
	 * set to expire one year in the future.
	 */
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    // For very general mappings (e.g. "/") we need to check 404 first
    Resource resource = getResource(request);
    if (resource == null) {
        logger.trace("No matching resource found - returning 404");
        response.sendError(HttpServletResponse.SC_NOT_FOUND);
        return;
    }
    if (HttpMethod.OPTIONS.matches(request.getMethod())) {
        response.setHeader("Allow", getAllowHeader());
        return;
    }
    // Supported methods and required session
    checkRequest(request);
    // Header phase
    if (new ServletWebRequest(request, response).checkNotModified(resource.lastModified())) {
        logger.trace("Resource not modified - returning 304");
        return;
    }
    // Apply cache settings, if any
    prepareResponse(response);
    // Check the media type for the resource
    MediaType mediaType = getMediaType(request, resource);
    if (mediaType != null) {
        if (logger.isTraceEnabled()) {
            logger.trace("Determined media type '" + mediaType + "' for " + resource);
        }
    } else {
        if (logger.isTraceEnabled()) {
            logger.trace("No media type found for " + resource + " - not sending a content-type header");
        }
    }
    // Content phase
    if (METHOD_HEAD.equals(request.getMethod())) {
        setHeaders(response, resource, mediaType);
        logger.trace("HEAD request - skipping content");
        return;
    }
    ServletServerHttpResponse outputMessage = new ServletServerHttpResponse(response);
    if (request.getHeader(HttpHeaders.RANGE) == null) {
        setHeaders(response, resource, mediaType);
        this.resourceHttpMessageConverter.write(resource, mediaType, outputMessage);
    } else {
        response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes");
        ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(request);
        try {
            List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
            response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
            if (httpRanges.size() == 1) {
                ResourceRegion resourceRegion = httpRanges.get(0).toResourceRegion(resource);
                this.resourceRegionHttpMessageConverter.write(resourceRegion, mediaType, outputMessage);
            } else {
                this.resourceRegionHttpMessageConverter.write(HttpRange.toResourceRegions(httpRanges, resource), mediaType, outputMessage);
            }
        } catch (IllegalArgumentException ex) {
            response.setHeader("Content-Range", "bytes */" + resource.contentLength());
            response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
        }
    }
}
Also used : ServletServerHttpRequest(org.springframework.http.server.ServletServerHttpRequest) ResourceRegion(org.springframework.core.io.support.ResourceRegion) Resource(org.springframework.core.io.Resource) MediaType(org.springframework.http.MediaType) ServletServerHttpResponse(org.springframework.http.server.ServletServerHttpResponse) ServletWebRequest(org.springframework.web.context.request.ServletWebRequest) HttpRange(org.springframework.http.HttpRange)

Example 4 with HttpRange

use of org.springframework.http.HttpRange in project spring-framework by spring-projects.

the class DefaultServerRequestTests method header.

@Test
public void header() {
    HttpHeaders httpHeaders = new HttpHeaders();
    List<MediaType> accept = Collections.singletonList(MediaType.APPLICATION_JSON);
    httpHeaders.setAccept(accept);
    List<Charset> acceptCharset = Collections.singletonList(StandardCharsets.UTF_8);
    httpHeaders.setAcceptCharset(acceptCharset);
    long contentLength = 42L;
    httpHeaders.setContentLength(contentLength);
    MediaType contentType = MediaType.TEXT_PLAIN;
    httpHeaders.setContentType(contentType);
    InetSocketAddress host = InetSocketAddress.createUnresolved("localhost", 80);
    httpHeaders.setHost(host);
    List<HttpRange> range = Collections.singletonList(HttpRange.createByteRange(0, 42));
    httpHeaders.setRange(range);
    DefaultServerRequest request = new DefaultServerRequest(MockServerWebExchange.from(MockServerHttpRequest.method(HttpMethod.GET, "https://example.com?foo=bar").headers(httpHeaders)), this.messageReaders);
    ServerRequest.Headers headers = request.headers();
    assertThat(headers.accept()).isEqualTo(accept);
    assertThat(headers.acceptCharset()).isEqualTo(acceptCharset);
    assertThat(headers.contentLength()).isEqualTo(OptionalLong.of(contentLength));
    assertThat(headers.contentType()).isEqualTo(Optional.of(contentType));
    assertThat(headers.header(HttpHeaders.CONTENT_TYPE)).containsExactly(MediaType.TEXT_PLAIN_VALUE);
    assertThat(headers.firstHeader(HttpHeaders.CONTENT_TYPE)).isEqualTo(MediaType.TEXT_PLAIN_VALUE);
    assertThat(headers.asHttpHeaders()).isEqualTo(httpHeaders);
}
Also used : HttpHeaders(org.springframework.http.HttpHeaders) InetSocketAddress(java.net.InetSocketAddress) MediaType(org.springframework.http.MediaType) Charset(java.nio.charset.Charset) HttpRange(org.springframework.http.HttpRange) Test(org.junit.jupiter.api.Test) ParameterizedTest(org.junit.jupiter.params.ParameterizedTest)

Example 5 with HttpRange

use of org.springframework.http.HttpRange in project spring-framework by spring-projects.

the class AbstractMessageConverterMethodProcessor method writeWithMessageConverters.

/**
 * Writes the given return type to the given output message.
 * @param value the value to write to the output message
 * @param returnType the type of the value
 * @param inputMessage the input messages. Used to inspect the {@code Accept} header.
 * @param outputMessage the output message to write to
 * @throws IOException thrown in case of I/O errors
 * @throws HttpMediaTypeNotAcceptableException thrown when the conditions indicated
 * by the {@code Accept} header on the request cannot be met by the message converters
 * @throws HttpMessageNotWritableException thrown if a given message cannot
 * be written by a converter, or if the content-type chosen by the server
 * has no compatible converter.
 */
@SuppressWarnings({ "rawtypes", "unchecked" })
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType, ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
    Object body;
    Class<?> valueType;
    Type targetType;
    if (value instanceof CharSequence) {
        body = value.toString();
        valueType = String.class;
        targetType = String.class;
    } else {
        body = value;
        valueType = getReturnValueType(body, returnType);
        targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());
    }
    if (isResourceType(value, returnType)) {
        outputMessage.getHeaders().set(HttpHeaders.ACCEPT_RANGES, "bytes");
        if (value != null && inputMessage.getHeaders().getFirst(HttpHeaders.RANGE) != null && outputMessage.getServletResponse().getStatus() == 200) {
            Resource resource = (Resource) value;
            try {
                List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
                outputMessage.getServletResponse().setStatus(HttpStatus.PARTIAL_CONTENT.value());
                body = HttpRange.toResourceRegions(httpRanges, resource);
                valueType = body.getClass();
                targetType = RESOURCE_REGION_LIST_TYPE;
            } catch (IllegalArgumentException ex) {
                outputMessage.getHeaders().set(HttpHeaders.CONTENT_RANGE, "bytes */" + resource.contentLength());
                outputMessage.getServletResponse().setStatus(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE.value());
            }
        }
    }
    MediaType selectedMediaType = null;
    MediaType contentType = outputMessage.getHeaders().getContentType();
    boolean isContentTypePreset = contentType != null && contentType.isConcrete();
    if (isContentTypePreset) {
        if (logger.isDebugEnabled()) {
            logger.debug("Found 'Content-Type:" + contentType + "' in response");
        }
        selectedMediaType = contentType;
    } else {
        HttpServletRequest request = inputMessage.getServletRequest();
        List<MediaType> acceptableTypes;
        try {
            acceptableTypes = getAcceptableMediaTypes(request);
        } catch (HttpMediaTypeNotAcceptableException ex) {
            int series = outputMessage.getServletResponse().getStatus() / 100;
            if (body == null || series == 4 || series == 5) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Ignoring error response content (if any). " + ex);
                }
                return;
            }
            throw ex;
        }
        List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);
        if (body != null && producibleTypes.isEmpty()) {
            throw new HttpMessageNotWritableException("No converter found for return value of type: " + valueType);
        }
        List<MediaType> mediaTypesToUse = new ArrayList<>();
        for (MediaType requestedType : acceptableTypes) {
            for (MediaType producibleType : producibleTypes) {
                if (requestedType.isCompatibleWith(producibleType)) {
                    mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
                }
            }
        }
        if (mediaTypesToUse.isEmpty()) {
            if (body != null) {
                throw new HttpMediaTypeNotAcceptableException(producibleTypes);
            }
            if (logger.isDebugEnabled()) {
                logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes);
            }
            return;
        }
        MimeTypeUtils.sortBySpecificity(mediaTypesToUse);
        for (MediaType mediaType : mediaTypesToUse) {
            if (mediaType.isConcrete()) {
                selectedMediaType = mediaType;
                break;
            } else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {
                selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
                break;
            }
        }
        if (logger.isDebugEnabled()) {
            logger.debug("Using '" + selectedMediaType + "', given " + acceptableTypes + " and supported " + producibleTypes);
        }
    }
    if (selectedMediaType != null) {
        selectedMediaType = selectedMediaType.removeQualityValue();
        for (HttpMessageConverter<?> converter : this.messageConverters) {
            GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
            if (genericConverter != null ? ((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) : converter.canWrite(valueType, selectedMediaType)) {
                body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType, (Class<? extends HttpMessageConverter<?>>) converter.getClass(), inputMessage, outputMessage);
                if (body != null) {
                    Object theBody = body;
                    LogFormatUtils.traceDebug(logger, traceOn -> "Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
                    addContentDispositionHeader(inputMessage, outputMessage);
                    if (genericConverter != null) {
                        genericConverter.write(body, targetType, selectedMediaType, outputMessage);
                    } else {
                        ((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
                    }
                } else {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Nothing to write: null body");
                    }
                }
                return;
            }
        }
    }
    if (body != null) {
        Set<MediaType> producibleMediaTypes = (Set<MediaType>) inputMessage.getServletRequest().getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
        if (isContentTypePreset || !CollectionUtils.isEmpty(producibleMediaTypes)) {
            throw new HttpMessageNotWritableException("No converter for [" + valueType + "] with preset Content-Type '" + contentType + "'");
        }
        throw new HttpMediaTypeNotAcceptableException(getSupportedMediaTypes(body.getClass()));
    }
}
Also used : HashSet(java.util.HashSet) Set(java.util.Set) HttpMediaTypeNotAcceptableException(org.springframework.web.HttpMediaTypeNotAcceptableException) InputStreamResource(org.springframework.core.io.InputStreamResource) Resource(org.springframework.core.io.Resource) ArrayList(java.util.ArrayList) HttpServletRequest(jakarta.servlet.http.HttpServletRequest) ResolvableType(org.springframework.core.ResolvableType) MediaType(org.springframework.http.MediaType) Type(java.lang.reflect.Type) HttpMessageNotWritableException(org.springframework.http.converter.HttpMessageNotWritableException) HttpMessageConverter(org.springframework.http.converter.HttpMessageConverter) GenericHttpMessageConverter(org.springframework.http.converter.GenericHttpMessageConverter) MediaType(org.springframework.http.MediaType) HttpRange(org.springframework.http.HttpRange) GenericHttpMessageConverter(org.springframework.http.converter.GenericHttpMessageConverter)

Aggregations

HttpRange (org.springframework.http.HttpRange)9 Test (org.junit.jupiter.api.Test)6 MediaType (org.springframework.http.MediaType)6 ResourceRegion (org.springframework.core.io.support.ResourceRegion)5 HttpHeaders (org.springframework.http.HttpHeaders)5 Resource (org.springframework.core.io.Resource)4 InetSocketAddress (java.net.InetSocketAddress)3 ArrayList (java.util.ArrayList)3 ClassPathResource (org.springframework.core.io.ClassPathResource)3 MockHttpOutputMessage (org.springframework.http.MockHttpOutputMessage)3 Charset (java.nio.charset.Charset)2 ParameterizedTest (org.junit.jupiter.params.ParameterizedTest)2 HttpServletRequest (jakarta.servlet.http.HttpServletRequest)1 ByteArrayInputStream (java.io.ByteArrayInputStream)1 Type (java.lang.reflect.Type)1 HashSet (java.util.HashSet)1 Set (java.util.Set)1 ResolvableType (org.springframework.core.ResolvableType)1 InputStreamResource (org.springframework.core.io.InputStreamResource)1 GenericHttpMessageConverter (org.springframework.http.converter.GenericHttpMessageConverter)1