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

org.robotframework.remoteserver.servlet.RemoteServerServlet Maven / Gradle / Ivy

There is a newer version: 3.6.0
Show newest version
/* Copyright 2014 Kevin Ormbrek
 * 
 * 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 org.robotframework.remoteserver.servlet;

import com.google.common.html.HtmlEscapers;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.xmlrpc.XmlRpcException;
import org.apache.xmlrpc.server.XmlRpcHandlerMapping;
import org.apache.xmlrpc.webserver.XmlRpcServlet;
import org.apache.xmlrpc.webserver.XmlRpcServletServer;
import org.robotframework.remoteserver.exceptions.IllegalPathException;
import org.robotframework.remoteserver.library.RemoteLibrary;
import org.robotframework.remoteserver.xmlrpc.ReflectiveHandlerMapping;
import org.robotframework.remoteserver.xmlrpc.TypeFactory;

/**
 * This servlet can be used with servlet containers such as GlassFish,
 * WebSphere, Tiny Java Web Server, etc. The paths for the library mapping are
 * relative to the servlet path. When a remote stop is performed,
 * System.exit(0) is executed.
 */
public class RemoteServerServlet extends XmlRpcServlet implements RemoteServerContext {

    private static final ThreadLocal request = new ThreadLocal<>();
    private static final ThreadLocal currLibrary = new ThreadLocal<>();
    private final Map libraryMap = new ConcurrentHashMap<>();

    /**
     * Cleans up the path of an incoming request. Repeating /s are reduced to
     * one /. Trailing /s are removed. A null or empty path is
     * converted to /.
     *
     * @param path the path the client requested
     * @return cleaned up path
     */
    protected static String cleanPath(String path) {
        if (path == null) {
            return "/";
        }
        if (!path.startsWith("/")) {
            path = "/" + path;
        }
        path = path.replaceAll("/+", "/");
        if (path.length() > 1 && path.endsWith("/")) {
            return path.substring(0, path.length() - 1);
        }
        return path;
    }

    protected static String checkPath(String path) {
        if (path == null || !path.startsWith("/")) {
            throw new IllegalPathException(String.format("Path [%s] does not start with a /.", path));
        } else if (path.contains("//")) {
            throw new IllegalPathException(String.format("Path [%s] contains repeated forward slashes.", path));
        } else if (!path.equals("/") && path.endsWith("/")) {
            throw new IllegalPathException(String.format("Path [%s] ends with a /.", path));
        } else if (!path.matches("[a-zA-Z0-9-._~/]+")) {
            throw new IllegalPathException(String.format(
                    "Path [%s] contains disallowed characters (must contain only alphanumeric or any of these: -._~/).",
                    path));
        }
        return path;
    }

    @Override public RemoteLibrary putLibrary(String path, RemoteLibrary library) {
        return libraryMap.put(checkPath(path), Objects.requireNonNull(library));
    }

    @Override public RemoteLibrary removeLibrary(String path) {
        return libraryMap.remove(path);
    }

    @Override public Map getLibraryMap() {
        return Collections.unmodifiableMap(libraryMap);
    }

    @Override protected XmlRpcServletServer newXmlRpcServer(ServletConfig pConfig) throws XmlRpcException {
        XmlRpcServletServer server = new XmlRpcServletServer();
        server.setTypeFactory(new TypeFactory(this.getXmlRpcServletServer()));
        return server;
    }

    @Override protected XmlRpcHandlerMapping newXmlRpcHandlerMapping() throws XmlRpcException {
        ReflectiveHandlerMapping map = new ReflectiveHandlerMapping();
        map.setRequestProcessorFactoryFactory(new RemoteServerRequestProcessorFactoryFactory(this));
        map.addHandler("keywords", ServerMethods.class);
        map.removePrefixes();
        return map;
    }

    @Override protected void service(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        try {
            request.set(req);
            super.service(req, resp);
        } finally {
            request.remove();
        }
    }

    @Override public void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        /*
         * when the client is Jython 2.5.x (old xmlrpclib using HTTP/1.0), the
         * server's sockets got stuck in FIN_WAIT_2 for some time, eventually
         * hitting the limit of open sockets on some Windows systems. adding
         * this header gets the web server to close the socket.
         */
        String path = req.getPathInfo() == null ? req.getServletPath() : req.getPathInfo();
        path = cleanPath(path);
        if (libraryMap.containsKey(path)) {
            currLibrary.set(libraryMap.get(path));
            if ("HTTP/1.0".equals(req.getProtocol()))
                resp.addHeader("Connection", "close");
            super.doPost(req, resp);
        } else {
            resp.sendError(HttpServletResponse.SC_NOT_FOUND, String.format("No library mapped to %s", path));
        }
    }

    @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        resp.setContentType("text/html");
        String body = getPage();
        resp.setContentLength(body.length());
        PrintWriter out = resp.getWriter();
        out.print(body);
    }

    @Override public HttpServletRequest getRequest() {
        return request.get();
    }

    protected String getPage() {
        Map map = new TreeMap<>(getLibraryMap());
        StringBuilder sb = new StringBuilder();
        sb.append(""
                + "jrobotremoteserver" + "

jrobotremoteserver serving:

" + ""); if (map.isEmpty()) { sb.append(""); } else { for (String path : map.keySet()) { sb.append(""); } } sb.append("
PathLibrary
No libraries mapped
"); sb.append(path); sb.append(""); sb.append(HtmlEscapers.htmlEscaper().escape(map.get(path).getURI())); sb.append("
"); return sb.toString(); } /** * Returns the library to use in the current context. This should only be * used while a request is being processed and only on the same thread that * is handling the request. * * @return the library to use in the current context */ public RemoteLibrary getLibrary() { return currLibrary.get(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy