All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.reactivex.netty.protocol.http.server.file.FileRequestHandler Maven / Gradle / Ivy

There is a newer version: 0.3.18
Show newest version
/*
 * Copyright 2014 Netflix, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.reactivex.netty.protocol.http.server.file;

import io.netty.buffer.ByteBuf;
import io.netty.channel.DefaultFileRegion;
import io.netty.handler.codec.http.HttpChunkedInput;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.stream.ChunkedFile;
import io.reactivex.netty.protocol.http.server.HttpError;
import io.reactivex.netty.protocol.http.server.HttpServerRequest;
import io.reactivex.netty.protocol.http.server.HttpServerResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rx.Observable;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.URI;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

import static io.netty.handler.codec.http.HttpHeaders.Names.CONNECTION;
import static io.netty.handler.codec.http.HttpHeaders.Names.IF_MODIFIED_SINCE;
import static io.netty.handler.codec.http.HttpHeaders.Values.KEEP_ALIVE;
import static io.netty.handler.codec.http.HttpMethod.GET;
import static io.netty.handler.codec.http.HttpResponseStatus.FORBIDDEN;
import static io.netty.handler.codec.http.HttpResponseStatus.METHOD_NOT_ALLOWED;
import static io.netty.handler.codec.http.HttpResponseStatus.NOT_FOUND;
import static io.netty.handler.codec.http.HttpResponseStatus.NOT_MODIFIED;
import static io.netty.handler.codec.http.HttpResponseStatus.OK;

/**
 * Base implementation for serving local files.  Resolving the request URI to
 * a local file URI is deferred to the subclass.
 * 
 * @author elandau
 *
 */
public abstract class FileRequestHandler extends AbstractFileRequestHandler {
    private static final Logger logger = LoggerFactory.getLogger(FileRequestHandler.class);
    
    private static final int CHUNK_SIZE = 8192;
    
    @Override
    public Observable handle(HttpServerRequest request, HttpServerResponse response) {
        // We don't support GET.  
        if (!request.getHttpMethod().equals(GET)) {
            return Observable.error(new HttpError(METHOD_NOT_ALLOWED));
        }
        
        RandomAccessFile raf = null;
        
        String sanitizedUri = sanitizeUri(request.getUri());
        if (sanitizedUri == null) {
            return Observable.error(new HttpError(FORBIDDEN));
        }
        
        URI uri = resolveUri(sanitizedUri);
        if (uri == null) {
            return Observable.error(new HttpError(NOT_FOUND));
        }
        
        File file = new File(uri);
        if (file.isHidden() || !file.exists()) {
            return Observable.error(new HttpError(NOT_FOUND));
        }

        if (file.isDirectory()) {
            return Observable.error(new HttpError(FORBIDDEN));
        }
        
        if (!file.isFile()) {
            return Observable.error(new HttpError(FORBIDDEN));
        }

        long fileLength;
        try {
            raf = new RandomAccessFile(file, "r");
            fileLength = raf.length();
        }
        catch (Exception e) {
            logger.warn("Error accessing file {}", uri, e);
            if (raf != null) {
                try {
                    raf.close();
                } catch (IOException e1) {
                    logger.warn("Error closing file {}", uri, e1);
                }
            }
            return Observable.error(e);
        }
        
        // Cache Validation
        String ifModifiedSince = request.getHeaders().get(IF_MODIFIED_SINCE);
        if (ifModifiedSince != null && !ifModifiedSince.isEmpty()) {
            SimpleDateFormat dateFormatter = new SimpleDateFormat(HTTP_DATE_FORMAT, Locale.US);
            Date ifModifiedSinceDate = null;
            try {
                ifModifiedSinceDate = dateFormatter.parse(ifModifiedSince);
            } catch (ParseException e) {
                logger.warn("Failed to parse {} header", IF_MODIFIED_SINCE);
            }

            if (ifModifiedSinceDate != null) {
                // Only compare up to the second because the datetime format we send to the client
                // does not have milliseconds
                long ifModifiedSinceDateSeconds = ifModifiedSinceDate.getTime() / 1000;
                long fileLastModifiedSeconds = file.lastModified() / 1000;
                if (ifModifiedSinceDateSeconds == fileLastModifiedSeconds) {
                    response.setStatus(NOT_MODIFIED);
                    setDateHeader(response, dateFormatter);
                    return response.close();
                }
            }
        }
        
        response.setStatus(OK);
        response.getHeaders().setContentLength(fileLength);
        setContentTypeHeader(response, file);
        setDateAndCacheHeaders(response, file);
        
        if (request.getHeaders().isKeepAlive()) {
            response.getHeaders().set(CONNECTION, KEEP_ALIVE);
        }
        
        if (response.getChannel().pipeline().get(SslHandler.class) == null) {
            response.writeFileRegion(new DefaultFileRegion(raf.getChannel(), 0, fileLength));
        }
        else {
            try {
                response.writeChunkedInput(new HttpChunkedInput(new ChunkedFile(raf, 0, fileLength, CHUNK_SIZE)));
            } catch (IOException e) {
                logger.warn("Failed to write chunked file {}", e);
                return Observable.error(e);
            }
        }
        
        return response.close();
    }

    protected abstract URI resolveUri(String path);
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy