diff --git a/libs/commons-codec-1.6.jar b/libs/commons-codec-1.6.jar new file mode 100644 index 0000000..ee1bc49 Binary files /dev/null and b/libs/commons-codec-1.6.jar differ diff --git a/libs/commons-logging-1.1.3.jar b/libs/commons-logging-1.1.3.jar new file mode 100644 index 0000000..ab51254 Binary files /dev/null and b/libs/commons-logging-1.1.3.jar differ diff --git a/libs/httpclient-4.3.5.jar b/libs/httpclient-4.3.5.jar new file mode 100644 index 0000000..1db1225 Binary files /dev/null and b/libs/httpclient-4.3.5.jar differ diff --git a/libs/httpcore-4.3.2.jar b/libs/httpcore-4.3.2.jar new file mode 100644 index 0000000..813ec23 Binary files /dev/null and b/libs/httpcore-4.3.2.jar differ diff --git a/program.own b/program.own index 859a3c3..4cc1cfd 100644 --- a/program.own +++ b/program.own @@ -124,4 +124,15 @@ nums = [1,2,3,4,5,6,7,8,9,10] nums = filter(nums, def(x) = x % 2 == 0) squares = map(nums, def(x) = x * x) foreach(squares, ::echo) -println "Sum: " + reduce(squares, 0, def(x, y) = x + y) \ No newline at end of file +println "Sum: " + reduce(squares, 0, def(x, y) = x + y) + +use "http" + +http("http://jsonplaceholder.typicode.com/users", "POST", {"name": "OwnLang", "versionCode": 10}, def(v) { + println "Added: " + v + http("http://jsonplaceholder.typicode.com/users/2", "PATCH", {"name": "Patched Name"}, ::http_get_demo) +}) +def http_get_demo(v) { + println "Updated: " + v + http("http://jsonplaceholder.typicode.com/users", ::echo) +} diff --git a/src/com/annimon/ownlang/lib/FunctionValue.java b/src/com/annimon/ownlang/lib/FunctionValue.java index ac2b49f..b2544f5 100644 --- a/src/com/annimon/ownlang/lib/FunctionValue.java +++ b/src/com/annimon/ownlang/lib/FunctionValue.java @@ -7,6 +7,8 @@ import java.util.Objects; * @author aNNiMON */ public final class FunctionValue implements Value { + + public static final FunctionValue EMPTY = new FunctionValue(args -> NumberValue.ZERO); private final Function value; diff --git a/src/com/annimon/ownlang/lib/MapValue.java b/src/com/annimon/ownlang/lib/MapValue.java index c55bd94..3eff1c7 100644 --- a/src/com/annimon/ownlang/lib/MapValue.java +++ b/src/com/annimon/ownlang/lib/MapValue.java @@ -11,6 +11,8 @@ import java.util.Objects; */ public class MapValue implements Value, Iterable> { + public static final MapValue EMPTY = new MapValue(1); + private final Map map; public MapValue(int size) { @@ -29,6 +31,10 @@ public class MapValue implements Value, Iterable> { public int size() { return map.size(); } + + public boolean containsKey(Value key) { + return map.containsKey(key); + } public Value get(Value key) { return map.get(key); diff --git a/src/com/annimon/ownlang/lib/StringValue.java b/src/com/annimon/ownlang/lib/StringValue.java index 59fed48..f38206f 100644 --- a/src/com/annimon/ownlang/lib/StringValue.java +++ b/src/com/annimon/ownlang/lib/StringValue.java @@ -14,6 +14,10 @@ public final class StringValue implements Value { this.value = value; } + public int length() { + return value.length(); + } + @Override public int type() { return Types.STRING; diff --git a/src/com/annimon/ownlang/lib/modules/functions/http_http.java b/src/com/annimon/ownlang/lib/modules/functions/http_http.java new file mode 100644 index 0000000..50df52e --- /dev/null +++ b/src/com/annimon/ownlang/lib/modules/functions/http_http.java @@ -0,0 +1,173 @@ +package com.annimon.ownlang.lib.modules.functions; + +import com.annimon.ownlang.lib.*; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.charset.UnsupportedCharsetException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.NameValuePair; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.*; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.BasicResponseHandler; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.message.BasicNameValuePair; + +public final class http_http implements Function { + + private static final Value + HEADER_KEY = new StringValue("header"), + CHARSET_KEY = new StringValue("charset"); + + @Override + public Value execute(Value... args) { + String url, method; + switch (args.length) { + case 1: // http(url) + url = args[0].asString(); + return process(url, "GET"); + + case 2: // http(url, method) || http(url, callback) + url = args[0].asString(); + if (args[1].type() == Types.FUNCTION) { + return process(url, "GET", (FunctionValue) args[1]); + } + return process(url, args[1].asString()); + + case 3: // http(url, method, params) || http(url, method, callback) + url = args[0].asString(); + method = args[1].asString(); + if (args[2].type() == Types.FUNCTION) { + return process(url, method, (FunctionValue) args[2]); + } + return process(url, method, args[2], FunctionValue.EMPTY); + + case 4: // http(url, method, params, callback) + if (args[3].type() != Types.FUNCTION) { + throw new RuntimeException("Fourth arg must be a function callback"); + } + url = args[0].asString(); + method = args[1].asString(); + return process(url, method, args[2], (FunctionValue) args[3]); + + case 5: // http(url, method, params, headerParams, callback) + if (args[3].type() != Types.MAP) { + throw new RuntimeException("Third arg must be a map"); + } + if (args[4].type() != Types.FUNCTION) { + throw new RuntimeException("Fifth arg must be a function callback"); + } + url = args[0].asString(); + method = args[1].asString(); + return process(url, method, args[2], (MapValue) args[3], (FunctionValue) args[4]); + + default: + throw new RuntimeException("Wrong number of arguments"); + } + } + + private Value process(String url, String method) { + return process(url, method, FunctionValue.EMPTY); + } + + private Value process(String url, String method, FunctionValue function) { + return process(url, method, MapValue.EMPTY, function); + } + + private Value process(String url, String method, Value params, FunctionValue function) { + return process(url, method, params, MapValue.EMPTY, function); + } + + private Value process(String url, String method, Value requestParams, MapValue options, FunctionValue function) { + final Function callback = function.getValue(); + try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) { + HttpRequestBase httpMethod; + switch (method.toUpperCase()) { + case "POST": + httpMethod = new HttpPost(url); + break; + case "PUT": + httpMethod = new HttpPut(url); + break; + case "DELETE": + httpMethod = new HttpDelete(url); + break; + case "PATCH": + httpMethod = new HttpPatch(url); + break; + case "HEAD": + httpMethod = new HttpHead(url); + break; + case "OPTIONS": + httpMethod = new HttpOptions(url); + break; + case "TRACE": + httpMethod = new HttpTrace(url); + break; + case "GET": + default: + httpMethod = new HttpGet(url); + break; + } + + if (options.containsKey(HEADER_KEY)) { + applyHeaderParams((MapValue) options.get(HEADER_KEY), httpMethod); + } + + if (httpMethod instanceof HttpEntityEnclosingRequestBase) { + final HttpEntityEnclosingRequestBase heerb = (HttpEntityEnclosingRequestBase) httpMethod; + if (requestParams.type() == Types.MAP) { + applyMapRequestParams(heerb, (MapValue) requestParams, options); + } else { + applyStringRequestParams(heerb, requestParams, options); + } + } + + final HttpResponse httpResponse = httpClient.execute(httpMethod); + final String response = new BasicResponseHandler().handleResponse(httpResponse); + callback.execute(new StringValue(response)); + return NumberValue.fromBoolean(true); + } catch (IOException ex) { + return NumberValue.fromBoolean(false); + } + } + + private void applyHeaderParams(MapValue headerParams, HttpRequestBase httpMethod) { + for (Map.Entry p : headerParams) { + httpMethod.addHeader(p.getKey().asString(), p.getValue().asString()); + } + } + + private void applyMapRequestParams(HttpEntityEnclosingRequestBase h, MapValue params, MapValue options) + throws UnsupportedEncodingException { + final List entityParams = new ArrayList<>(params.size()); + for (Map.Entry param : params) { + final String name = param.getKey().asString(); + final String value = param.getValue().asString(); + entityParams.add(new BasicNameValuePair(name, value)); + } + HttpEntity entity; + if (options.containsKey(CHARSET_KEY)) { + entity = new UrlEncodedFormEntity(entityParams, options.get(CHARSET_KEY).asString()); + } else { + entity = new UrlEncodedFormEntity(entityParams); + } + h.setEntity(entity); + } + + private void applyStringRequestParams(final HttpEntityEnclosingRequestBase heerb, Value requestParams, MapValue options) throws UnsupportedEncodingException, UnsupportedCharsetException { + HttpEntity entity; + if (options.containsKey(CHARSET_KEY)) { + entity = new StringEntity(requestParams.asString(), options.get(CHARSET_KEY).asString()); + } else { + entity = new StringEntity(requestParams.asString()); + } + heerb.setEntity(entity); + } + +} \ No newline at end of file diff --git a/src/com/annimon/ownlang/lib/modules/functions/http_urlencode.java b/src/com/annimon/ownlang/lib/modules/functions/http_urlencode.java new file mode 100644 index 0000000..02ff8ff --- /dev/null +++ b/src/com/annimon/ownlang/lib/modules/functions/http_urlencode.java @@ -0,0 +1,27 @@ +package com.annimon.ownlang.lib.modules.functions; + +import com.annimon.ownlang.lib.Function; +import com.annimon.ownlang.lib.StringValue; +import com.annimon.ownlang.lib.Value; +import java.io.IOException; +import java.net.URLEncoder; + +public final class http_urlencode implements Function { + + @Override + public Value execute(Value... args) { + if (args.length == 0) throw new RuntimeException("At least one arg expected"); + + String charset = "UTF-8"; + if (args.length >= 2) { + charset = args[1].asString(); + } + + try { + final String result = URLEncoder.encode(args[0].asString(), charset); + return new StringValue(result); + } catch (IOException ex) { + return args[0]; + } + } +} \ No newline at end of file diff --git a/src/com/annimon/ownlang/lib/modules/http.java b/src/com/annimon/ownlang/lib/modules/http.java new file mode 100644 index 0000000..1138cbd --- /dev/null +++ b/src/com/annimon/ownlang/lib/modules/http.java @@ -0,0 +1,17 @@ +package com.annimon.ownlang.lib.modules; + +import com.annimon.ownlang.lib.*; +import com.annimon.ownlang.lib.modules.functions.*; + +/** + * + * @author aNNiMON + */ +public final class http implements Module { + + @Override + public void init() { + Functions.set("urlencode", new http_urlencode()); + Functions.set("http", new http_http()); + } +}