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

com.intuit.karate.http.ServerContext Maven / Gradle / Ivy

There is a newer version: 1.4.1
Show newest version
/*
 * The MIT License
 *
 * Copyright 2020 Intuit Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package com.intuit.karate.http;

import com.intuit.karate.FileUtils;
import com.intuit.karate.JsonUtils;
import com.intuit.karate.LogAppender;
import com.intuit.karate.core.Variable;
import com.intuit.karate.graal.JsArray;
import com.intuit.karate.graal.JsEngine;
import com.intuit.karate.graal.JsValue;
import com.intuit.karate.graal.Methods;
import com.intuit.karate.template.KarateEngineContext;
import com.intuit.karate.template.TemplateUtils;
import io.netty.handler.codec.http.cookie.Cookie;
import io.netty.handler.codec.http.cookie.ServerCookieDecoder;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import org.graalvm.polyglot.Value;
import org.graalvm.polyglot.proxy.ProxyObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author pthomas3
 */
public class ServerContext implements ProxyObject {

    private static final Logger logger = LoggerFactory.getLogger(ServerContext.class);

    private static final String READ = "read";
    private static final String RESOLVER = "resolver";
    private static final String READ_AS_STRING = "readAsString";
    private static final String EVAL = "eval";
    private static final String EVAL_WITH = "evalWith";
    private static final String GET = "get";
    private static final String LOG = "log";
    private static final String UUID = "uuid";
    private static final String REMOVE = "remove";
    private static final String SWITCH = "switch";
    private static final String SWITCHED = "switched";
    private static final String AJAX = "ajax";
    private static final String HTTP = "http";
    private static final String NEXT_ID = "nextId";
    private static final String SESSION_ID = "sessionId";
    private static final String CLOSE = "close";
    private static final String CLOSED = "closed";
    private static final String RENDER = "render";
    private static final String BODY_APPEND = "bodyAppend";
    private static final String COPY = "copy";
    private static final String TO_LIST = "toList";
    private static final String TO_JSON = "toJson";
    private static final String TO_JSON_PRETTY = "toJsonPretty";
    private static final String FROM_JSON = "fromJson";
    private static final String TEMPLATE = "template";
    private static final String TYPE_OF = "typeOf";
    private static final String IS_PRIMITIVE = "isPrimitive";

    private static final String[] KEYS = new String[]{
        READ, RESOLVER, READ_AS_STRING, EVAL, EVAL_WITH, GET, LOG, UUID, REMOVE, SWITCH, SWITCHED, AJAX, HTTP, NEXT_ID, SESSION_ID,
        CLOSE, CLOSED, RENDER, BODY_APPEND, COPY, TO_LIST, TO_JSON, TO_JSON_PRETTY, FROM_JSON, TEMPLATE, TYPE_OF, IS_PRIMITIVE};
    private static final Set KEY_SET = new HashSet(Arrays.asList(KEYS));
    private static final JsArray KEY_ARRAY = new JsArray(KEYS);

    private final ServerConfig config;
    private final Request request;

    private boolean stateless;
    private boolean api;
    private boolean httpGetAllowed;
    private boolean lockNeeded;
    private boolean newSession;
    private Session session; // can be pre-resolved, else will be set by RequestCycle.init()
    private boolean switched;
    private boolean closed;
    private Supplier customHandler;
    private int nextId;

    private final Map variables;
    private List bodyAppends;
    private LogAppender logAppender;
    private RequestCycle mockRequestCycle;

    public ServerContext(ServerConfig config, Request request) {
        this(config, request, null);
    }

    public ServerContext(ServerConfig config, Request request, Map variables) {
        this.config = config;
        this.request = request;
        this.variables = variables;
        HTTP_FUNCTION = args -> {
            HttpClient client = config.getHttpClientFactory().apply(request);
            HttpRequestBuilder http = new HttpRequestBuilder(client);
            if (args.length > 0) {
                http.url((String) args[0]);
            }
            return http;
        };
        RENDER_FUNCTION = o -> {
            if (o instanceof String) {
                JsEngine je = RequestCycle.get().getEngine();
                return TemplateUtils.renderResourcePath((String) o, je, config.getResourceResolver());
            }
            Map map;
            if (o instanceof Map) {
                map = (Map) o;
            } else {
                logger.warn("invalid argument to render: {}", o);
                return null;
            }
            Map templateVars = (Map) map.get("variables");
            String path = (String) map.get("path");
            String html = (String) map.get("html");
            Boolean fork = (Boolean) map.get("fork");
            Object swap = map.get("swap");
            if (path == null && html == null) {
                logger.warn("invalid argument to render, 'path' or 'html' needed: {}", map);
                return null;
            }
            JsEngine je;
            if (fork != null && fork) {
                je = JsEngine.local();
            } else {
                je = RequestCycle.get().getEngine().copy();
            }
            if (templateVars != null) {
                je.putAll(templateVars);
            }
            String body;
            if (path != null) {
                body = TemplateUtils.renderResourcePath(path, je, config.getResourceResolver());
            } else {
                body = TemplateUtils.renderHtmlString(html, je, config.getResourceResolver());
            }
            if (swap != null) {
                String id = (String) map.get("id");
                StringBuilder sb = new StringBuilder();
                sb.append("");
                sb.append(body);
                sb.append("
"); String appended = sb.toString(); bodyAppend(appended); } return body; }; } public String getSessionCookieValue() { List values = request.getHeaderValues(HttpConstants.HDR_COOKIE); if (values == null) { return null; } for (String value : values) { Set cookies = ServerCookieDecoder.STRICT.decode(value); for (Cookie c : cookies) { if (config.getSessionCookieName().equals(c.name())) { return c.value(); } } } return null; } public String readAsString(String resource) { InputStream is = config.getResourceResolver().resolve(resource).getStream(); return FileUtils.toString(is); } public Object read(String resource) { String raw = readAsString(resource); ResourceType resourceType = ResourceType.fromFileExtension(resource); if (resourceType == ResourceType.JS) { return eval(raw); } else { return JsValue.fromString(raw, false, resourceType); } } public Object eval(String source) { return RequestCycle.get().getEngine().evalForValue(source); } public Object evalWith(Object o, String source) { Value value = Value.asValue(o); return RequestCycle.get().getEngine().evalWith(value, source, true); } public String toJson(Object o) { Value value = Value.asValue(o); return new JsValue(value).toJsonOrXmlString(false); } public String toJsonPretty(Object o) { Value value = Value.asValue(o); return new JsValue(value).toJsonOrXmlString(true); } public ServerConfig getConfig() { return config; } public Request getRequest() { return request; } public Map getVariables() { return variables; } public boolean isNewSession() { return newSession; } public void setNewSession(boolean newSession) { this.newSession = newSession; } public Session getSession() { return session; } public void setSession(Session session) { this.session = session; } public boolean isLockNeeded() { return lockNeeded; } public void setLockNeeded(boolean lockNeeded) { this.lockNeeded = lockNeeded; } public boolean isStateless() { return stateless; } public void setStateless(boolean stateless) { this.stateless = stateless; } public boolean isAjax() { return request.isAjax(); } public boolean isApi() { return api; } public void setApi(boolean api) { this.api = api; } public boolean isClosed() { return closed; } public boolean isHttpGetAllowed() { return httpGetAllowed; } public void setHttpGetAllowed(boolean httpGetAllowed) { this.httpGetAllowed = httpGetAllowed; } public Supplier getCustomHandler() { return customHandler; } public void setCustomHandler(Supplier customHandler) { this.customHandler = customHandler; } public void setMockRequestCycle(RequestCycle mockRequestCycle) { this.mockRequestCycle = mockRequestCycle; } public RequestCycle getMockRequestCycle() { return mockRequestCycle; } public boolean isSwitched() { return switched; } public List getBodyAppends() { return bodyAppends; } public void bodyAppend(String body) { if (bodyAppends == null) { bodyAppends = new ArrayList(); } bodyAppends.add(body); } public LogAppender getLogAppender() { return logAppender; } public void setLogAppender(LogAppender logAppender) { this.logAppender = logAppender; } public void log(Object... args) { String log = new LogWrapper(args).toString(); logger.info(log); if (logAppender != null) { logAppender.append(log); } } private final Methods.FunVar GET_FUNCTION = args -> { if (args.length == 0 || args[0] == null) { return null; } String name = args[0].toString(); KarateEngineContext kec = KarateEngineContext.get(); if (args.length == 1) { return kec.getVariable(name); } if (kec.containsVariable(name)) { return kec.getVariable(name); } else { return args[1]; } }; private static final Supplier UUID_FUNCTION = () -> java.util.UUID.randomUUID().toString(); private static final Function FROM_JSON_FUNCTION = s -> JsValue.fromString(s, false, null); private final Methods.FunVar HTTP_FUNCTION; // set in constructor private final Function RENDER_FUNCTION; // set in constructor private final Methods.FunVar LOG_FUNCTION = args -> { log(args); return null; }; private final Function COPY_FUNCTION = o -> { return JsValue.fromJava(JsonUtils.deepCopy(o)); }; private final Function TO_LIST_FUNCTION = o -> { if (o instanceof Map) { Map map = (Map) o; List list = JsonUtils.toList(map); return JsValue.fromJava(list); } else { logger.warn("unable to cast to map: {} - {}", o.getClass(), o); return null; } }; private final Methods.FunVar SWITCH_FUNCTION = args -> { if (switched) { logger.warn("context.switch() can be called only once during a request, ignoring: {}", args[0]); } else { switched = true; // flag for request cycle render KarateEngineContext.get().setRedirect(true); // flag for template engine RequestCycle rc = RequestCycle.get(); if (args.length > 1) { Value value = Value.asValue(args[1]); if (value.hasMembers()) { JsValue jv = new JsValue(value); rc.setSwitchParams(jv.getAsMap()); } } String template; if (args.length > 0) { template = args[0].toString(); rc.setSwitchTemplate(template); } else { template = null; } throw new RedirectException(template); } return null; }; private final Supplier CLOSE_FUNCTION = () -> { closed = true; return null; }; private static final BiFunction REMOVE_FUNCTION = (o, k) -> { if (o instanceof Map && k != null) { Map in = (Map) o; Map out = new HashMap(in); Object removed = out.remove(k.toString()); if (removed == null) { logger.warn("nothing removed, key not present: {}", k); return o; } else { return JsValue.fromJava(out); } } else if (o != null) { logger.warn("unable to cast to map: {} - {}", o.getClass(), o); } return o; }; private final Supplier NEXT_ID_FUNCTION = () -> ++nextId + "-" + System.currentTimeMillis(); private final Function TYPE_OF_FUNCTION = o -> new Variable(o).getTypeString(); private final Function IS_PRIMITIVE_FUNCTION = o -> !new Variable(o).isMapOrList(); @Override public Object getMember(String key) { switch (key) { case READ: return (Function) this::read; case READ_AS_STRING: return (Function) this::readAsString; case EVAL: return (Function) this::eval; case EVAL_WITH: return (BiFunction) this::evalWith; case GET: return GET_FUNCTION; case LOG: return LOG_FUNCTION; case UUID: return UUID_FUNCTION; case COPY: return COPY_FUNCTION; case TO_LIST: return TO_LIST_FUNCTION; case TO_JSON: return (Function) this::toJson; case TO_JSON_PRETTY: return (Function) this::toJsonPretty; case FROM_JSON: return FROM_JSON_FUNCTION; case REMOVE: return REMOVE_FUNCTION; case SWITCH: return SWITCH_FUNCTION; case SWITCHED: return switched; case AJAX: return isAjax(); case HTTP: return HTTP_FUNCTION; case NEXT_ID: return NEXT_ID_FUNCTION; case SESSION_ID: return session == null ? null : session.getId(); case CLOSE: return CLOSE_FUNCTION; case CLOSED: return closed; case RENDER: return RENDER_FUNCTION; case BODY_APPEND: return (Consumer) this::bodyAppend; case RESOLVER: return config.getResourceResolver(); case TEMPLATE: return KarateEngineContext.get().getTemplateName(); case TYPE_OF: return TYPE_OF_FUNCTION; case IS_PRIMITIVE: return IS_PRIMITIVE_FUNCTION; default: logger.warn("no such property on context object: {}", key); return null; } } @Override public Object getMemberKeys() { return KEY_ARRAY; } @Override public boolean hasMember(String key) { return KEY_SET.contains(key); } @Override public void putMember(String key, Value value) { logger.warn("put not supported on context object: {} - {}", key, value); } static class LogWrapper { // TODO code duplication with ScenarioBridge final Object[] values; LogWrapper(Object... values) { // sometimes a null array gets passed in, graal weirdness this.values = values == null ? new Value[0] : values; } @Override public String toString() { StringBuilder sb = new StringBuilder(); for (Object v : values) { Variable var = new Variable(v); sb.append(var.getAsPrettyString()).append(' '); } return sb.toString(); } } }



© 2015 - 2025 Weber Informatics LLC | Privacy Policy