为了证明有关 Lisp(特别是 Common Lisp)的实用性的互联网错误,我试图让简单(但现实)的网络应用程序运行起来,这是一种荒谬的尝试。四天后,ABCL 的补丁我得到了一些工作。 (let* ((port 8080) (server (make-server 8080))) (route server "GET" "/" (lambda (ctx) "My index!")) (route server "GET" "/search" ( lambda (ctx) (模板 "search.tmpl" '(("version" "0.1.0") ("results" ("cat" "dog" "mouse"))))))) <html> <title>版本 {{ version }}</title> {% for item in results %} <h2>{{ item }}</h2> {% endfor %}</html> Armed Bear Common Lisp (ABCL) 是唯一的 Common Lispimplementation 我知道它可以连接到像 JVM 或 CLR 这样的主要库生态系统。从理论上讲,这对于那些想要生态系统的稳定性和资源的人来说是一个安全的建议,即使他们不使用其旗舰语言。像 Micronaut(和 Jersey)这样的库的问题在于它们做了大量的动态检查来弄清楚如何注册控制器等等。这对于使用 Java 库的开发人员来说当然很方便。但是,当您尝试通过另一种语言的外部函数接口 (FFI) 使用库时,这将成为一种折磨。例如,如果框架扫描目录中的所有文件以获取 @GET 注释。另一方面,Spark 对引入 Websocket 库有一个看似严格的要求,这在配置过程中导致了一些问题。所以我最终选择了 Jooby 和 Netty(作为底层服务器)。最后,我查看了一些类似 Jinja 的模板库并选择了 Pebble,因为 Jinjava 不会加载表单。
<?xml version="1.0" encoding="utf-8"?><project> <modelVersion>4.0.0</modelVersion> <groupId>com.github.eatonphil</groupId> <artifactId>abcl-rest-api -hello-world</artifactId> <version>1</version> <dependencies> <dependency> <groupId>io.jooby</groupId> <artifactId>jooby</artifactId> <version>2.10.0</version> </dependency> <dependency> <groupId>io.jooby</groupId> <artifactId>jooby-netty</artifactId> <version>2.10.0</version> </dependency> <dependency> <groupId>io.pebbletemplates </groupId> <artifactId>pebble</artifactId> <version>3.1.5</version> </dependency> </dependencies></project> ABCL 有一个名为 abcl-asdf 的包,它可以帮助您通过 Maven 和你的文件系统。我们将导入它和它所依赖的包 (abcl-contrib):要从 Maven 导入特定包,您可以调用 abcl-asdf:resolve 并使用包含 Maven 包组 ID 和工件 ID 的冒号分隔字符串调用 abcl-asdf:resolve。然后将该结果传递给 abcl-asdf:as-classpath 并将该结果传递给 java:add-to-classpath。现在您可以在这些包中调用函数。如果您只想使用内置函数调用 Java 方法,它看起来像 (jcall "method""com.organization.package.Class" object arg1 arg2 ... argN)。如果要调用静态 Java 方法,请使用 (jstatic...) 而不是 (jcall ...)。似乎 ABCL 会自动将简单类型从 theirLisp 表示形式转换为 Java,但它不会将列表转换为数组。如果 Java 函数需要数组,则必须使用类似 (java:jnew-array-from-list"java.lang.String" my-string-list) 之类的函数明确地执行此操作。使用内置 Java FFI 时,您始终需要对 java.lang.Objectfor Object 或 java.util.Arrayfor Array 等类使用完全限定名称。或者,您可以(需要 :jss)访问用于进行 Java 调用的更简单的语法。方法调用看起来像 (#"method" object arg1 arg2 ... argN)。创建对象的新实例正在调用 (jss:jnew'className)。当您使用 JSS 时,您不需要完全限定类名,除非有多个类同名。例如,要创建一个新的 Jooby 应用程序实例,我们可以调用 (jss:jnew 'Jooby)。只要在类路径中可以找到该类,JSS 就会解析它。
真正的代码将类似于本文顶部的伪代码。我们将剔除特定于库的包装器,用于呈现模板和注册路由。 * 服务器服务器 = new Netty(); // 或 Jetty 或 Utow * * App app = new App(); * * server.start(app); * * ... * * server.stop(); Netty 来自 Maven 上 io.jooby 组中的 jooby-netty 工件。而 App 是扩展 io.jooby.Jooby 的对象。由于我们没有使用 OOP 语言,但我们将尽量避免使用类。因此,我们将创建一个新的 io.jooby.Jooby 实例并直接向其添加路由。 (defun template (filename context) "")(defun route (app method path handler) nil)(defun register-endpoints (app) (route app "GET" "/" (lambda (ctx) "An index!")) (route app "GET" "/search" (lambda (ctx) (template "search.tmpl" `(("version" "1.0.0") ("results" ,(java:jarray-from-list '(") cat" "dog" "mouse"))))))) (route app "GET" "/hello-world" (lambda (ctx) "Hello world!")))(let* ((port 8080)) (server (jss:new 'Netty)) (app (jss:new 'Jooby))) (register-endpoints app) (#"setOptions" server (#"setPort" (jss:new 'ServerOptions) port)) (#"start " server app) (#"join" server)) 我们再次不会像 Jooby 文档建议的那样使用花哨的 Java 语法(如果你使用 Java 很好)。再次搜索 Jooby 源代码看起来我们可以使用方法字符串、路径字符串和实现 io.jooby.Route.Handler 接口的对象实例调用 Jooby 类上的路由。由于这个处理程序参数是一个接口,我们不能通过创建它的实例来再次欺骗我们必须在 Lisp 中实际创建一个扩展它的新类。幸运的是,我们只需要实现一个方法来满足这个接口,apply。它接受一个 io.jooby.Context 对象并返回一个 java.lang.Object。然后,框架会进行自省,以确定对象到底是什么,以及是否需要将其转换为字符串以作为 HTTP 响应正文返回。要在 ABCL 中创建一个新类,我们调用 (java:jnew-runtime-class"classname" :interfaces '("an interface name") :methods '(("methodname 1" "return type" ("first parameter type" . ..) (lambda (this arg1 ...) body)))):
(defun route (app method path handler) (#"route" app method path (jss:new (java:jnew-runtime-class (substitute #\$ #\/ (substitute #\$ #\- path)) :interfaces '("io.jooby.Route$Handler") :methods `( ("apply" "java.lang.Object" ("io.jooby.Context") (lambda (this ctx) (funcall ,handler ctx))) ))))) 需要注意的一件事是,在文件中引用子类时,我们需要使用 io.jooby.Route$Handler 语法来处理它,而不是像您在 Java 中引用的 io.jooby.Route.Handler 那样。在后一种情况下,ABCLthinks Route 是一个包,而实际上它只是一个类。如果你现在用 abcl --load main.lisp 运行它。它会一直工作,直到您到达终点。问题是 Jooby 如何试图找出返回对象的真实类型。在这种情况下,它尝试打开并解析我们应用程序的(Java)源代码,以尝试找到此应用函数的返回类型。这是一个问题,因为我们的代码不是 Java。通过反复试验Irealized,我们可以通过添加另一个apply实现来向我们的类返回一个字符串,从而欺骗Jooby/Java/某人找出正确的返回类型。 (defun route (app method path handler) (#"route" app method path (jss:new (java:jnew-runtime-class (substitute #\$ #\/ (substitute #\$ #\- path)) :interfaces '("io.jooby.Route$Handler") :methods `(;; 需要定义这个来让 Jooby 找出返回类型;; 否则它会尝试读取不是 Java 文件的“这个文件”,所以无法解析 ("apply" "java.lang.String" ("io.jooby.Context") (lambda (this ctx) nil)) ;; 这个实际上被调用 ("apply" "java.lang.Object" ("io.jooby.Context") (lambda (this ctx) (funcall ,handler ctx)))))))) 你可能想知道,为什么保留原始方法?嗯,这是因为在反射过程中,ABCL 说在 Handlerinterface 中不存在这样的返回 String 的方法。我想这很公平。
PebbleEngine engine = new PebbleEngine.Builder().build();PebbleTemplatecompiledTemplate = engine.getTemplate("home.html");Map<String, Object> context = new HashMap<>();context.put("name" , "Mitchell");Writer writer = new StringWriter();compiledTemplate.evaluate(writer, context);String output = writer.toString(); (defun hashmap (alist) (let ((map (jss:new 'HashMap))) (loop for el in alist do (#"put" map (car el) (cadr el))) map))(defun template ( filename context-alist) (let* ((ctx (hashmap context-alist)) (path (java:jstatic "of" "java.nio.file.Path" filename)) (file (#"readString" 'java.nio .file.Files path)) (engine (#"build" (jss:new 'PebbleEngine$Builder))) (compiledTmpl (#"getTemplate" engine filename)) (writer (jss:new 'java.io.StringWriter)) ) (#"evaluate"compiledTmpl writer ctx) (#"toString" writer))) 但是如果你运行这个 abcl --load main.lisp 并点击这个 /search 端点,它会爆炸说“没有这样的方法”存在于对 Path.of(filename) 的调用。虽然有使用可变参数函数的例子,当函数只有一个参数,比如 java.util.Arrays.asList(T ...),在这里使用相同的技术继续导致“没有这样的方法”:最终我找到了一个例子有人在这种函数调用上进行反射/调用,在 ABCL 源代码的本地副本上尝试了这种逻辑。 (defun 模板 (filename context-alist) (let* ((ctx (hashmap context-alist)) (path (java:jstatic "of" "java.nio.file.Path" 文件名 (java:jnew-array "java. lang.String" 0))) (file (#"readString" 'java.nio.file.Files path)) (engine (#"build" (jss:new 'PebbleEngine$Builder))) (compiledTmpl (#"getTemplate) " 引擎文件名)) (writer (jss:new 'java.io.StringWriter))) (#"evaluate"compiledTmpl writer ctx) (#"toString" writer))) $ mkdir ~/vendor$ cd ~/vendor$ git克隆 https://github.com/eatonphil/abcl$ cd abcl$ git checkout pe/more-variadic$ sudo {dnf/brew/apt} install ant maven$ ant -f build.xml
我正在将此示例移植到 Kawa 以查看它的表现。博客文章来了。为了证明互联网关于 Lisp(特别是 Common Lisp)的实用性是错误的,我试图让一个简单(但现实)的网络应用程序运行起来,这是一种荒谬的尝试。经过四天和 ABCL 的补丁,我得到了一些工作。 https://t.co/5UUWNR8Wnn pic.twitter.com/cZsx32IlKD — Phil Eaton (@phil_eaton) 2021 年 8 月 5 日