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

io.jsync.http.impl.DefaultHttpServerResponse Maven / Gradle / Ivy

There is a newer version: 1.10.13
Show newest version
/*
 * Copyright (c) 2011-2013 The original author or authors
 * ------------------------------------------------------
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * and Apache License v2.0 which accompanies this distribution.
 *
 *     The Eclipse Public License is available at
 *     http://www.eclipse.org/legal/epl-v10.html
 *
 *     The Apache License v2.0 is available at
 *     http://www.opensource.org/licenses/apache2.0.php
 *
 * You may elect to redistribute this code under either of these licenses.
 */

package io.jsync.http.impl;

import io.jsync.AsyncResult;
import io.jsync.Handler;
import io.jsync.MultiMap;
import io.jsync.buffer.Buffer;
import io.jsync.file.impl.PathAdjuster;
import io.jsync.http.HttpServerResponse;
import io.jsync.impl.AsyncInternal;
import io.jsync.impl.DefaultFutureResult;
import io.jsync.utils.MimeUtils;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.handler.codec.http.*;

import java.io.File;

/**
 * @author Tim Fox
 */
public class DefaultHttpServerResponse implements HttpServerResponse {

    private static final Buffer NOT_FOUND = new Buffer("Resource not found");
    private static final Buffer FORBIDDEN = new Buffer("Forbidden");

    private final AsyncInternal async;
    private final ServerConnection conn;
    private final HttpResponse response;
    private final HttpVersion version;
    private final boolean keepAlive;
    private boolean headWritten;
    private boolean written;
    private Handler drainHandler;
    private Handler exceptionHandler;
    private Handler closeHandler;
    private boolean chunked;
    private boolean closed;
    private ChannelFuture channelFuture;
    private MultiMap headers;
    private LastHttpContent trailing;
    private MultiMap trailers;
    private String statusMessage;

    DefaultHttpServerResponse(final AsyncInternal async, ServerConnection conn, HttpRequest request) {
        this.async = async;
        this.conn = conn;
        this.version = request.getProtocolVersion();
        this.response = new DefaultHttpResponse(version, HttpResponseStatus.OK, false);
        this.keepAlive = version == HttpVersion.HTTP_1_1 ||
                (version == HttpVersion.HTTP_1_0 && request.headers().contains(io.jsync.http.HttpHeaders.CONNECTION, io.jsync.http.HttpHeaders.KEEP_ALIVE, true));
    }

    @Override
    public MultiMap headers() {
        if (headers == null) {
            headers = new HttpHeadersAdapter(response.headers());
        }
        return headers;
    }

    @Override
    public MultiMap trailers() {
        if (trailers == null) {
            if (trailing == null) {
                trailing = new DefaultLastHttpContent(Unpooled.EMPTY_BUFFER, false);
            }
            trailers = new HttpHeadersAdapter(trailing.trailingHeaders());
        }
        return trailers;
    }

    @Override
    public int getStatusCode() {
        return response.getStatus().code();
    }

    @Override
    public HttpServerResponse setStatusCode(int statusCode) {
        HttpResponseStatus status = statusMessage != null ? new HttpResponseStatus(statusCode, statusMessage) : HttpResponseStatus.valueOf(statusCode);
        this.response.setStatus(status);
        return this;
    }

    @Override
    public HttpServerResponse setContentLength(int contentLength) {
        checkWritten();
        headers().set(HttpHeaders.Names.CONTENT_LENGTH, Integer.toString(contentLength));

        return this;
    }

    @Override
    public HttpServerResponse setContentType(String contentType) {
        checkWritten();
        headers().set(HttpHeaders.Names.CONTENT_TYPE, contentType);

        return this;
    }

    @Override
    public String getStatusMessage() {
        return response.getStatus().reasonPhrase();
    }

    @Override
    public HttpServerResponse setStatusMessage(String statusMessage) {
        this.statusMessage = statusMessage;
        this.response.setStatus(new HttpResponseStatus(response.getStatus().code(), statusMessage));
        return this;
    }

    @Override
    public boolean isChunked() {
        return chunked;
    }

    @Override
    public DefaultHttpServerResponse setChunked(boolean chunked) {
        checkWritten();
        // HTTP 1.0 does not support chunking so we ignore this if HTTP 1.0
        if (version != HttpVersion.HTTP_1_0) {
            this.chunked = chunked;
        }
        return this;
    }

    @Override
    public DefaultHttpServerResponse putHeader(String key, String value) {
        checkWritten();
        headers().set(key, value);
        return this;
    }

    @Override
    public DefaultHttpServerResponse putHeader(String key, Iterable values) {
        checkWritten();
        headers().set(key, values);
        return this;
    }

    @Override
    public DefaultHttpServerResponse putTrailer(String key, String value) {
        checkWritten();
        trailers().set(key, value);
        return this;
    }

    @Override
    public DefaultHttpServerResponse putTrailer(String key, Iterable values) {
        checkWritten();
        trailers().set(key, values);
        return this;
    }

    @Override
    public HttpServerResponse putHeader(CharSequence name, CharSequence value) {
        checkWritten();
        headers().set(name, value);
        return this;
    }

    @Override
    public HttpServerResponse putHeader(CharSequence name, Iterable values) {
        checkWritten();
        headers().set(name, values);
        return this;
    }

    @Override
    public HttpServerResponse putTrailer(CharSequence name, CharSequence value) {
        checkWritten();
        trailers().set(name, value);
        return this;
    }

    @Override
    public HttpServerResponse putTrailer(CharSequence name, Iterable value) {
        checkWritten();
        trailers().set(name, value);
        return this;
    }

    @Override
    public HttpServerResponse setWriteQueueMaxSize(int size) {
        checkWritten();
        conn.doSetWriteQueueMaxSize(size);
        return this;
    }

    @Override
    public boolean writeQueueFull() {
        checkWritten();
        return conn.doWriteQueueFull();
    }

    @Override
    public HttpServerResponse drainHandler(Handler handler) {
        checkWritten();
        this.drainHandler = handler;
        conn.handleInterestedOpsChanged(); //If the channel is already drained, we want to call it immediately
        return this;
    }

    @Override
    public HttpServerResponse exceptionHandler(Handler handler) {
        checkWritten();
        this.exceptionHandler = handler;
        return this;
    }

    @Override
    public HttpServerResponse closeHandler(Handler handler) {
        checkWritten();
        this.closeHandler = handler;
        return this;
    }

    @Override
    public DefaultHttpServerResponse write(Buffer chunk) {
        ByteBuf buf = chunk.getByteBuf();
        return write(buf, null);
    }

    @Override
    public DefaultHttpServerResponse write(String chunk, String enc) {
        return write(new Buffer(chunk, enc).getByteBuf(), null);
    }

    @Override
    public DefaultHttpServerResponse write(String chunk) {
        return write(new Buffer(chunk).getByteBuf(), null);
    }

    @Override
    public void end(String chunk) {
        end(new Buffer(chunk));
    }

    @Override
    public void end(String chunk, String enc) {
        end(new Buffer(chunk, enc));
    }

    @Override
    public void end(Buffer chunk) {
        if (!chunked && !contentLengthSet()) {
            headers().set(io.jsync.http.HttpHeaders.CONTENT_LENGTH, String.valueOf(chunk.length()));
        }
        ByteBuf buf = chunk.getByteBuf();
        end0(buf);
    }

    @Override
    public void close() {
        if (!closed) {
            if (headWritten) {
                closeConnAfterWrite();
            } else {
                conn.close();
            }
            closed = true;
        }
    }

    @Override
    public void end() {
        end0(Unpooled.EMPTY_BUFFER);
    }

    private void end0(ByteBuf data) {
        checkWritten();

        if (!headWritten) {
            // if the head was not written yet we can write out everything in on go
            // which is more cheap.
            prepareHeaders();
            FullHttpResponse resp;
            if (trailing != null) {
                resp = new AssembledFullHttpResponse(response, data, trailing.trailingHeaders(), trailing.getDecoderResult());
            } else {
                resp = new AssembledFullHttpResponse(response, data);
            }
            channelFuture = conn.write(resp);
            headWritten = true;
        } else {
            if (!data.isReadable()) {
                if (trailing == null) {
                    channelFuture = conn.write(LastHttpContent.EMPTY_LAST_CONTENT);
                } else {
                    channelFuture = conn.write(trailing);
                }
            } else {
                LastHttpContent content;
                if (trailing != null) {
                    content = new AssembledLastHttpContent(data, trailing.trailingHeaders(), trailing.getDecoderResult());
                } else {
                    content = new DefaultLastHttpContent(data, false);
                }
                channelFuture = conn.write(content);
            }
        }

        if (!keepAlive) {
            closeConnAfterWrite();
        }
        written = true;
        conn.responseComplete();
    }

    @Override
    public DefaultHttpServerResponse sendFile(String filename) {
        return sendFile(filename, (String) null);
    }

    @Override
    public DefaultHttpServerResponse sendFile(String filename, String notFoundResource) {
        doSendFile(filename, notFoundResource, null);
        return this;
    }

    @Override
    public HttpServerResponse sendFile(String filename, Handler> resultHandler) {
        return sendFile(filename, null, resultHandler);
    }

    @Override
    public HttpServerResponse sendFile(String filename, String notFoundFile, Handler> resultHandler) {
        doSendFile(filename, notFoundFile, resultHandler);
        return this;
    }

    private void doSendFile(String filename, String notFoundResource, final Handler> resultHandler) {
        if (headWritten) {
            throw new IllegalStateException("Head already written");
        }
        checkWritten();
        File file = new File(PathAdjuster.adjust(async, filename));
        if (!file.exists()) {
            if (notFoundResource != null) {
                setStatusCode(HttpResponseStatus.NOT_FOUND.code());
                sendFile(notFoundResource, (String) null, resultHandler);
            } else {
                sendNotFound();
            }
        } else if (file.isDirectory()) {
            // send over a 403 Forbidden
            sendForbidden();
        } else {
            if (!contentLengthSet()) {
                putHeader(io.jsync.http.HttpHeaders.CONTENT_LENGTH, String.valueOf(file.length()));
            }
            if (!contentTypeSet()) {
                int li = filename.lastIndexOf('.');
                if (li != -1 && li != filename.length() - 1) {
                    String ext = filename.substring(li + 1, filename.length());
                    String contentType = MimeUtils.getMimeTypeForExtension(ext);
                    if (contentType != null) {
                        putHeader(io.jsync.http.HttpHeaders.CONTENT_TYPE, contentType);
                    }
                }
            }
            prepareHeaders();
            conn.queueForWrite(response);
            conn.sendFile(file);

            // write an empty last content to let the http encoder know the response is complete
            channelFuture = conn.write(LastHttpContent.EMPTY_LAST_CONTENT);
            headWritten = written = true;

            if (resultHandler != null) {
                channelFuture.addListener(new ChannelFutureListener() {
                    public void operationComplete(ChannelFuture future) throws Exception {
                        final AsyncResult res;
                        if (future.isSuccess()) {
                            res = new DefaultFutureResult<>((Void) null);
                        } else {
                            res = new DefaultFutureResult<>(future.cause());
                        }
                        async.runOnContext(new Handler() {
                            @Override
                            public void handle(Void v) {
                                resultHandler.handle(res);
                            }
                        });
                    }
                });
            }

            if (!keepAlive) {
                closeConnAfterWrite();
            }
            conn.responseComplete();
        }
    }


    private boolean contentLengthSet() {
        if (headers == null) {
            return false;
        }
        return response.headers().contains(io.jsync.http.HttpHeaders.CONTENT_LENGTH);
    }

    private boolean contentTypeSet() {
        if (headers == null) {
            return false;
        }
        return response.headers().contains(io.jsync.http.HttpHeaders.CONTENT_TYPE);
    }

    private void closeConnAfterWrite() {
        if (channelFuture != null) {
            channelFuture.addListener(new ChannelFutureListener() {
                public void operationComplete(ChannelFuture future) throws Exception {
                    conn.close();
                }
            });
        }
    }

    private void sendForbidden() {
        setStatusCode(HttpResponseStatus.FORBIDDEN.code());
        putHeader(io.jsync.http.HttpHeaders.CONTENT_TYPE, io.jsync.http.HttpHeaders.TEXT_HTML);
        end(FORBIDDEN);
    }

    private void sendNotFound() {
        setStatusCode(HttpResponseStatus.NOT_FOUND.code());
        putHeader(io.jsync.http.HttpHeaders.CONTENT_TYPE, io.jsync.http.HttpHeaders.TEXT_HTML);
        end(NOT_FOUND);
    }

    void handleDrained() {
        if (drainHandler != null) {
            drainHandler.handle(null);
        }
    }

    void handleException(Throwable t) {
        if (exceptionHandler != null) {
            exceptionHandler.handle(t);
        }
    }

    void handleClosed() {
        if (closeHandler != null) {
            closeHandler.handle(null);
        }
    }

    private void checkWritten() {
        if (written) {
            throw new IllegalStateException("Response has already been written");
        }
    }

    private void prepareHeaders() {
        if (version == HttpVersion.HTTP_1_0 && keepAlive) {
            response.headers().set(io.jsync.http.HttpHeaders.CONNECTION, io.jsync.http.HttpHeaders.KEEP_ALIVE);
        }
        if (chunked) {
            response.headers().set(io.jsync.http.HttpHeaders.TRANSFER_ENCODING, io.jsync.http.HttpHeaders.CHUNKED);
        } else if (version != HttpVersion.HTTP_1_0 && !contentLengthSet()) {
            response.headers().set(io.jsync.http.HttpHeaders.CONTENT_LENGTH, "0");
        }
    }


    private DefaultHttpServerResponse write(ByteBuf chunk, final Handler> doneHandler) {
        checkWritten();
        if (!headWritten && version != HttpVersion.HTTP_1_0 && !chunked && !contentLengthSet()) {
            throw new IllegalStateException("You must set the Content-Length header to be the total size of the message "
                    + "body BEFORE sending any data if you are not using HTTP chunked encoding.");
        }

        if (!headWritten) {
            prepareHeaders();
            channelFuture = conn.write(new AssembledHttpResponse(response, chunk));
            headWritten = true;
        } else {
            channelFuture = conn.write(new DefaultHttpContent(chunk));
        }

        conn.addFuture(doneHandler, channelFuture);
        return this;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy