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

org.yamcs.http.HandlerContext Maven / Gradle / Ivy

There is a newer version: 5.10.9
Show newest version
package org.yamcs.http;

import static io.netty.handler.codec.http.HttpHeaderNames.ALLOW;
import static io.netty.handler.codec.http.HttpHeaderNames.AUTHORIZATION;
import static io.netty.handler.codec.http.HttpHeaderNames.CACHE_CONTROL;
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH;
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpHeaderNames.HOST;
import static io.netty.handler.codec.http.HttpHeaderNames.LOCATION;
import static io.netty.handler.codec.http.HttpHeaderValues.TEXT_HTML;
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.URLDecoder;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.yamcs.security.User;
import org.yamcs.templating.ParseException;
import org.yamcs.templating.TemplateProcessor;
import org.yamcs.utils.Mimetypes;

import com.google.common.io.ByteStreams;
import com.google.common.io.CharStreams;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import com.google.protobuf.Message;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufOutputStream;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.handler.codec.http.multipart.Attribute;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
import io.netty.handler.codec.http.multipart.InterfaceHttpData;
import io.netty.handler.codec.http.multipart.InterfaceHttpData.HttpDataType;
import io.netty.handler.ssl.SslHandler;

public class HandlerContext {

    private static final Mimetypes MIME = Mimetypes.getInstance();

    private final String contextPath;
    private final ChannelHandlerContext nettyContext;
    private final HttpRequest nettyRequest;
    private final QueryStringDecoder qsDecoder;
    private final User user;

    private Map formParameters;

    public HandlerContext(String contextPath, ChannelHandlerContext ctx, HttpRequest req, User user) {
        this.contextPath = contextPath;
        nettyContext = ctx;
        nettyRequest = req;
        this.user = user;
        qsDecoder = new QueryStringDecoder(req.uri());
    }

    public User getUser() {
        return user;
    }

    /**
     * Attempts to derive the externally used URL to Yamcs based on request information
     * 
     * @return a url of the form [protocol]://[host]:[port][context]
     */
    public String getRequestBaseURL() {
        boolean tls = nettyContext.channel().pipeline().get(SslHandler.class) != null;
        String forwardedProto = nettyRequest.headers().get("x-forwarded-proto");
        if ("https".equals(forwardedProto)) {
            tls = true;
        }

        String host;
        int port = tls ? 443 : 80;

        String hostURL = nettyRequest.headers().get("x-forwarded-host");
        if (hostURL == null) {
            hostURL = nettyRequest.headers().get(HOST);
        }

        if (hostURL != null) {
            int idx = hostURL.lastIndexOf(':');
            if (idx == -1) {
                host = hostURL;
            } else {
                host = hostURL.substring(0, idx);
                port = Integer.parseInt(hostURL.substring(idx + 1));
            }
        } else {
            InetSocketAddress address = (InetSocketAddress) nettyContext.channel().remoteAddress();
            host = address.getHostName();
            port = address.getPort();
        }

        if (tls) {
            return String.format("https://%s%s", port == 443 ? host : host + ":" + port, contextPath);
        } else {
            return String.format("http://%s%s", port == 80 ? host : host + ":" + port, contextPath);
        }
    }

    public String getOriginalHostAddress() {
        var forwardedFor = nettyRequest.headers().get("x-forwarded-for");
        if (forwardedFor != null) {
            return forwardedFor;
        } else {
            var address = (InetSocketAddress) nettyContext.channel().remoteAddress();
            return address.getAddress().getHostAddress();
        }
    }

    public String getOriginalHostName() {
        var ipAddress = getOriginalHostAddress();
        try {
            var inetAddress = InetAddress.getByName(ipAddress);
            return inetAddress.getHostName();
        } catch (UnknownHostException e) {
            return ipAddress;
        }
    }

    public ChannelHandlerContext getNettyChannelHandlerContext() {
        return nettyContext;
    }

    public HttpRequest getNettyHttpRequest() {
        return nettyRequest;
    }

    /**
     * @throws ClassCastException
     *             if this context does not contain a "full" HTTP request.
     */
    public FullHttpRequest getNettyFullHttpRequest() {
        return (FullHttpRequest) nettyRequest;
    }

    public String getContextPath() {
        return contextPath;
    }

    public String getPathWithoutContext() {
        return HttpUtils.getPathWithoutContext(nettyRequest, contextPath);
    }

    public boolean isGET() {
        return nettyRequest.method() == HttpMethod.GET;
    }

    public boolean isPOST() {
        return nettyRequest.method() == HttpMethod.POST;
    }

    public void requireGET() {
        requireMethod(HttpMethod.GET);
    }

    public void requirePOST() {
        requireMethod(HttpMethod.POST);
    }

    public void requireMethod(HttpMethod... allowedMethods) {
        for (HttpMethod allowedMethod : allowedMethods) {
            if (nettyRequest.method() == allowedMethod) {
                return;
            }
        }
        throw new MethodNotAllowedException(nettyRequest.method(), nettyRequest.uri(),
                Arrays.asList(allowedMethods));
    }

    public void requireFormEncoding() {
        if (!isFormEncoded()) {
            throw new BadRequestException("Request is not form-encoded");
        }
    }

    public String requireFormParameter(String parameter) {
        String value = getFormParameter(parameter);
        if (value == null) {
            throw new BadRequestException("Missing form parameter '" + parameter + "'");
        }
        return value;
    }

    public String requireQueryParameter(String parameter) {
        String value = getQueryParameter(parameter);
        if (value == null) {
            throw new BadRequestException("Missing query parameter '" + parameter + "'");
        }
        return value;
    }

    public String requireParameter(String parameter) {
        String value = getParameter(parameter);
        if (value == null) {
            throw new BadRequestException("Missing parameter '" + parameter + "'");
        }
        return value;
    }

    public String getHeader(CharSequence name) {
        return nettyRequest.headers().get(name);
    }

    public boolean isFormEncoded() {
        return "application/x-www-form-urlencoded".equals(nettyRequest.headers().get(CONTENT_TYPE));
    }

    public String getCredentials(String type) {
        String authorizationHeader = getHeader(AUTHORIZATION);
        if (authorizationHeader != null) {
            String prefix = type + " ";
            if (authorizationHeader.startsWith(prefix)) {
                return authorizationHeader.substring(prefix.length());
            }
        }
        return null;
    }

    public String[] getBasicCredentials() {
        String userpassEncoded = getCredentials("Basic");
        if (userpassEncoded != null) {
            String userpassDecoded;
            try {
                userpassDecoded = new String(Base64.getDecoder().decode(userpassEncoded));
            } catch (IllegalArgumentException e) {
                throw new BadRequestException("Could not decode Base64-encoded credentials");
            }
            String[] parts = userpassDecoded.split(":", 2);
            if (parts.length < 2) {
                throw new BadRequestException("Malformed username/password (Not separated by colon?)");
            }
            try {
                return new String[] { URLDecoder.decode(parts[0], "UTF-8"), URLDecoder.decode(parts[1], "UTF-8") };
            } catch (UnsupportedEncodingException e) {
                throw new InternalServerErrorException(e);
            }
        }
        return null;
    }

    public String getFormParameter(String parameter) {
        if (!isFormEncoded()) {
            return null;
        }

        if (formParameters == null) {
            formParameters = new HashMap<>();
            HttpPostRequestDecoder formDecoder = new HttpPostRequestDecoder(nettyRequest);
            try {
                for (InterfaceHttpData d : formDecoder.getBodyHttpDatas()) {
                    if (d.getHttpDataType() == HttpDataType.Attribute) {
                        formParameters.put(d.getName(), ((Attribute) d).getValue());
                    }
                }
            } catch (IOException e) {
                throw new InternalServerErrorException(e);
            } finally {
                formDecoder.destroy();
            }
        }
        return formParameters.get(parameter);
    }

    public String getQueryParameter(String parameter) {
        List matches = qsDecoder.parameters().get(parameter);
        if (matches == null || matches.isEmpty()) {
            return null;
        } else {
            return matches.get(0);
        }
    }

    public String getParameter(String parameter) {
        String match = getQueryParameter(parameter);
        if (match == null && isFormEncoded()) {
            return getFormParameter(parameter);
        }
        return match;
    }

    public ByteBuf createByteBuf() {
        return nettyContext.alloc().buffer();
    }

    public void renderOK(String templateResource, Map vars) {
        render(HttpResponseStatus.OK, templateResource, vars);
    }

    public void render(HttpResponseStatus status, String templateResource, Map vars) {
        String processed = renderToString(templateResource, vars);

        ByteBuf body = nettyContext.alloc().buffer();
        body.writeCharSequence(processed, StandardCharsets.UTF_8);
        HttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, status, body);
        response.headers().set(CONTENT_TYPE, TEXT_HTML);
        response.headers().set(CONTENT_LENGTH, body.readableBytes());
        sendResponse(response);
    }

    public String renderToString(String templateResource, Map vars) {
        try (InputStream resource = HandlerContext.class.getResourceAsStream(templateResource);
                InputStreamReader reader = new InputStreamReader(resource, StandardCharsets.UTF_8)) {
            String template = CharStreams.toString(reader);
            return TemplateProcessor.process(template, vars);
        } catch (IOException | ParseException e) {
            throw new InternalServerErrorException(e);
        }
    }

    public void sendOK() {
        HttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.OK);
        response.headers().set(CONTENT_LENGTH, 0);
        sendResponse(response);
    }

    public void sendOK(Message message) {
        HttpRequestHandler.sendMessageResponse(nettyContext, nettyRequest, HttpResponseStatus.OK, message);
    }

    public void sendOK(JsonObject jsonObject) {
        ByteBuf body = nettyContext.alloc().buffer();
        String json = new GsonBuilder().setPrettyPrinting().create().toJson(jsonObject);
        body.writeCharSequence(json, StandardCharsets.UTF_8);
        HttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.OK, body);
        response.headers().set(CONTENT_TYPE, "application/json");
        response.headers().set(CONTENT_LENGTH, body.readableBytes());
        HttpRequestHandler.sendResponse(nettyContext, nettyRequest, response);
    }

    public void sendAllow(HttpMethod... methods) {
        var response = new DefaultFullHttpResponse(HTTP_1_1, OK);
        response.headers().set(ALLOW, Arrays.asList(methods));
        response.headers().set(CONTENT_LENGTH, 0);
        sendResponse(response);
    }

    /**
     * Send a classpath resource.
     */
    public void sendResource(String resource) {
        var body = createByteBuf();
        try (var in = getClass().getResourceAsStream(resource)) {
            if (in == null) {
                throw new NotFoundException();
            }
            try (var out = new ByteBufOutputStream(body)) {
                ByteStreams.copy(in, out);
            }
        } catch (IOException e) {
            throw new InternalServerErrorException(e);
        }

        var response = new DefaultFullHttpResponse(HTTP_1_1, OK, body);
        response.headers().set(CONTENT_TYPE, MIME.getMimetype(resource));
        response.headers().set(CONTENT_LENGTH, body.readableBytes());
        response.headers().set(CACHE_CONTROL, "private, max-age=86400");
        HttpRequestHandler.sendResponse(nettyContext, nettyRequest, response);
    }

    public ChannelFuture sendResponse(HttpResponse response) {
        return HttpRequestHandler.sendResponse(nettyContext, nettyRequest, response);
    }

    public void sendRedirect(String location) {
        HttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.FOUND);
        response.headers().set(CONTENT_LENGTH, 0);
        response.headers().set(LOCATION, location);
        HttpRequestHandler.sendResponse(nettyContext, nettyRequest, response);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy