com.intuit.karate.http.ServerContext Maven / Gradle / Ivy
The newest version!
/*
* The MIT License
*
* Copyright 2022 Karate Labs 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.Match;
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.resource.Resource;
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.time.Instant;
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 SET = "set";
private static final String LOG = "log";
private static final String UUID = "uuid";
private static final String REMOVE = "remove";
private static final String REDIRECT = "redirect";
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 INIT = "init";
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 DELAY = "delay";
private static final String TO_STRING = "toString";
private static final String TO_JSON = "toJson";
private static final String TO_JS = "toJs";
private static final String TO_JSON_PRETTY = "toJsonPretty";
private static final String FROM_JSON = "fromJson";
private static final String CALLER = "caller";
private static final String TEMPLATE = "template";
private static final String TYPE_OF = "typeOf";
private static final String IS_PRIMITIVE = "isPrimitive";
private static final String MATCH = "match";
private static final String[] KEYS = new String[]{
READ, RESOLVER, READ_AS_STRING, EVAL, EVAL_WITH, GET, SET, LOG, UUID, REMOVE, REDIRECT, SWITCH, SWITCHED, AJAX, HTTP, NEXT_ID, SESSION_ID,
INIT, CLOSE, CLOSED, RENDER, BODY_APPEND, COPY, DELAY, TO_STRING, TO_JSON, TO_JS, TO_JSON_PRETTY, FROM_JSON,
CALLER, TEMPLATE, TYPE_OF, IS_PRIMITIVE, MATCH};
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 String redirectPath;
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) {
return TemplateUtils.renderHtmlResource((String) o, getEngine(), config.getResourceResolver(), config.isDevMode());
}
Map map;
if (o instanceof Map) {
map = (Map) o;
} else {
logger.warn("invalid argument to render: {}", o);
return null;
}
Map vars = (Map) map.get("vars");
String path = (String) map.get("path");
String html = (String) map.get("html");
Boolean fork = (Boolean) map.get("fork");
Boolean append = (Boolean) map.get("append");
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 = getEngine().copy();
}
if (vars != null) {
je.putAll(vars);
}
String body;
if (path != null) {
body = TemplateUtils.renderHtmlResource(path, je, config.getResourceResolver(), config.isDevMode());
} else {
body = TemplateUtils.renderHtmlString(html, je, config.getResourceResolver());
}
if (append != null && append) {
bodyAppend(body);
}
return body;
};
}
public boolean setApiIfPathStartsWith(String prefix) {
String path = request.getPath();
if (path.startsWith(prefix)) {
api = true;
int length = prefix.length();
int pos = path.indexOf('/', length);
if (pos != -1) {
request.setResourcePath(path.substring(0, pos) + ".js");
} else {
request.setResourcePath(path + ".js");
}
request.setPath(path.substring(length - 1));
return true;
}
return false;
}
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) {
if (resource.startsWith(Resource.THIS_COLON)) {
resource = resource.substring(Resource.THIS_COLON.length());
if (resource.charAt(0) != '/') {
resource = "/" + resource;
}
String path = request.getResourcePath();
int pos = path.lastIndexOf('/');
if (pos != -1) {
resource = path.substring(0, pos) + 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.fromJava(JsonUtils.fromString(raw, false, resourceType));
}
}
private JsEngine getEngine() {
KarateEngineContext kec = KarateEngineContext.get();
return kec == null ? RequestCycle.get().getEngine() : kec.getJsEngine();
}
public Object eval(String source) {
return getEngine().evalForValue(source);
}
public Object evalWith(Object o, String source) {
Value value = Value.asValue(o);
return getEngine().evalWith(value, source, true);
}
public String toJson(Object o) {
Value value = Value.asValue(o);
return new JsValue(value).toJsonOrXmlString(false);
}
public Object toJs(Object o) {
return JsValue.fromJava(o);
}
public String toJsonPretty(Object o) {
Value value = Value.asValue(o);
String pretty = new JsValue(value).toJsonOrXmlString(true);
return pretty == null ? null : pretty.trim();
}
public ServerConfig getConfig() {
return config;
}
public Request getRequest() {
return request;
}
public Map getVariables() {
return variables;
}
public boolean isNewSession() {
return newSession;
}
public void init() {
long now = Instant.now().getEpochSecond();
long expires = now + config.getSessionExpirySeconds();
session = config.getSessionStore().create(now, expires);
newSession = true;
}
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 String getRedirectPath() {
return redirectPath;
}
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();
Object value;
if (kec != null && kec.containsVariable(name)) {
value = kec.getVariable(name);
} else {
JsEngine je = getEngine();
if (je.bindings.hasMember(name)) {
value = je.get(name).getValue();
} else if (args.length > 1) {
value = args[1];
} else {
value = null;
}
}
return value;
};
private Void setVariable(String name, Object value) {
getEngine().put(name, value);
return null;
}
private static final Supplier UUID_FUNCTION = () -> java.util.UUID.randomUUID().toString();
private static final Function FROM_JSON_FUNCTION = s -> JsonUtils.fromString(s, false, null);
private final Methods.FunVar HTTP_FUNCTION; // set in constructor
private final Function