嵌入式 Jetty 服务器的 JNDI 查找失败

JNDI Lookup Failing For Embedded Jetty Server

我在我的一个项目中有一个集成测试,我想 运行 针对嵌入式码头服务器。我按照此处的示例进行操作:https://www.eclipse.org/jetty/documentation/jetty-9/index.html#jndi-embedded 但是当我实际进行 运行 测试时,它失败并显示错误:

javax.naming.NameNotFoundException: env is not bound; remaining name 'env/jdbc/NavDS'
    at org.eclipse.jetty.jndi.NamingContext.getContext(NamingContext.java:241)
    at org.eclipse.jetty.jndi.NamingContext.lookup(NamingContext.java:491)
    at org.eclipse.jetty.jndi.NamingContext.lookup(NamingContext.java:491)
    at org.eclipse.jetty.jndi.NamingContext.lookup(NamingContext.java:505)
    at org.eclipse.jetty.jndi.java.javaRootURLContext.lookup(javaRootURLContext.java:101)
    at javax.naming.InitialContext.lookup(InitialContext.java:417)
    at com.tura.eyerep.test.TestWebServices.setUpBeforeClass(TestWebServices.java:63)

我敢肯定我在某个地方犯了一个简单的错误,但我似乎无法发现它。任何人都可以提出我在这里做错了什么的建议吗?

在我的测试中,我正在设置服务器:

@BeforeClass
public static void setUpBeforeClass() throws Exception {
            
    server = new Server(8080);
    ClassList classList = ClassList.setServerDefault(server);
    classList.addAfter("org.eclipse.jetty.webapp.FragmentConfiguration", "org.eclipse.jetty.plus.webapp.EnvConfiguration", "org.eclipse.jetty.plus.webapp.PlusConfiguration");
    
    WebAppContext context = new WebAppContext();
    context.setExtractWAR(false);
    context.setDescriptor("src/main/webapp/WEB-INF/web.xml");
    context.setResourceBase("src/main/webapp");
    context.setConfigurationDiscovered(false);  
    
    BasicDataSource ds = null;      
    ds = new BasicDataSource();
    ds.setUrl("jdbc:h2:mem:myDB;create=true;MODE=MSSQLServer;DATABASE_TO_UPPER=FALSE;");
    org.eclipse.jetty.plus.jndi.Resource mydatasource = new org.eclipse.jetty.plus.jndi.Resource(context, "jdbc/NavDS",ds);
    
    server.setHandler(context);
    server.start();
}

@Test
public void testLookup()
{
    InitialContext ctx = new InitialContext();
    DataSource myds = (DataSource) ctx.lookup("java:comp/env/jdbc/NavDS");
    assertNotNull( myds);
}

在我的 web.xml 中,我有一个资源引用条目:

  <resource-ref>
    <description>Nav Datasource</description>
    <res-ref-name>jdbc/NavDS</res-ref-name>
    <res-type>javax.sql.DataSource</res-type>
    <res-auth>Container</res-auth>
  </resource-ref>

让我们先清理你的测试用例。

// meaningless when you have a WAR that is a directory.
context.setExtractWAR(false); // remove this line

// This prevents Servlet 3 behaviors when using Servlet 2.x descriptors
context.setConfigurationDiscovered(false); // Remove this

您遇到的错误...

javax.naming.NameNotFoundException: env is not bound; 
  remaining name 'env/jdbc/NavDS'

这可能意味着服务器仍在 运行 某处,可能忘记了 stop/cleanup 以前的服务器实例。 (查看下面的示例 junit5 测试用例,了解它如何处理非固定端口、如何引用非固定端口以及如何停止服务器的生命周期)。

接下来,注意你的范围。

new org.eclipse.jetty.plus.jndi.Resource(context, "jdbc/NavDS",ds);

这会将资源条目绑定到范围 context 上的 jdbc/NavDS

这意味着如果您在 WebAppContext 范围之外查找资源, 就像您使用 testLookup() 方法一样,它将存在于 initialContext.lookup("jdbc/NavDS"),而在其他任何地方,java:comp/env prefix/tree 甚至不存在于 testLookup() 方法范围内。

在您的 Web 应用程序内部,例如在过滤器或 Servlet 中,特定于上下文的资源在 jdbc:comp/env/jdbc/NavDS 处绑定并可用。

您有 3 个典型示波器。

Order Scope EnvEntry or Resource first parameter
1 WebApp new EnvEntry(webappContext, ...) or new Resource(webappContext, ...)
2 Server new EnvEntry(server, ...) or new Resource(server, ...)
3 JVM new EnvEntry(null, ...) or new Resource(null, ...)

如果该值在 WebApp 范围内不存在,则检查服务器范围,然后检查 JVM 范围。

您的服务器可以有名称 val/foo 的值,而特定的网络应用程序可以有相同名称 val/foo 不同 值,只需通过范围是如何定义的。

接下来是Servlet规范中的绑定,你指定了<resource-ref>,这与服务器端的声明相结合,绑定到context意味着你可以访问java:comp/env/jdbc/NavDS 来自该特定 webapp 中的 servlet。

要以不同的方式查看,请在代码中...

package jetty.jndi;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.net.HttpURLConnection;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.util.List;
import javax.naming.InitialContext;
import javax.naming.NameNotFoundException;
import javax.naming.NamingException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.plus.webapp.EnvConfiguration;
import org.eclipse.jetty.plus.webapp.PlusConfiguration;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.resource.PathResource;
import org.eclipse.jetty.webapp.Configuration;
import org.eclipse.jetty.webapp.FragmentConfiguration;
import org.eclipse.jetty.webapp.WebAppContext;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

public class WebAppWithJNDITest
{
    private static Server server;
    private static WebAppContext context;

    public static class JndiDumpServlet extends HttpServlet
    {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
        {
            resp.setCharacterEncoding("utf-8");
            resp.setContentType("text/plain");
            PrintStream out = new PrintStream(resp.getOutputStream(), false, StandardCharsets.UTF_8);
            try
            {
                dumpJndi(out);
            }
            catch (NamingException e)
            {
                throw new ServletException(e);
            }
        }
    }

    @BeforeAll
    public static void startServer() throws Exception
    {
        server = new Server(0); // let os/jvm pick a port
        Configuration.ClassList classList = Configuration.ClassList.setServerDefault(server);
        classList.addAfter(FragmentConfiguration.class.getName(),
            EnvConfiguration.class.getName(),
            PlusConfiguration.class.getName());

        context = new WebAppContext();
        context.setContextPath("/");
        // This directory only has WEB-INF/web.xml
        context.setBaseResource(new PathResource(Paths.get("src/main/webroots/jndi-root")));
        context.addServlet(JndiDumpServlet.class, "/jndi-dump");


        new org.eclipse.jetty.plus.jndi.Resource(null, "val/foo", Integer.valueOf(707));
        new org.eclipse.jetty.plus.jndi.Resource(server, "val/foo", Integer.valueOf(808));
        new org.eclipse.jetty.plus.jndi.Resource(context, "val/foo", Integer.valueOf(909));

        new org.eclipse.jetty.plus.jndi.EnvEntry(null, "entry/foo", Integer.valueOf(440), false);
        new org.eclipse.jetty.plus.jndi.EnvEntry(server, "entry/foo", Integer.valueOf(550), false);
        new org.eclipse.jetty.plus.jndi.EnvEntry(context, "entry/foo", Integer.valueOf(660), false);

        server.setHandler(context);
        server.start();
    }

    @AfterAll
    public static void stopServer()
    {
        LifeCycle.stop(server);
    }

    public static void dumpJndi(PrintStream out) throws NamingException
    {
        InitialContext ctx = new InitialContext();

        List<String> paths = List.of("val/foo", "entry/foo");
        List<String> prefixes = List.of("java:comp/env/", "");

        for (String prefix : prefixes)
        {
            for (String path : paths)
            {
                try
                {
                    Integer val = (Integer)ctx.lookup(prefix + path);
                    out.printf("lookup(\"%s%s\") = %s%n", prefix, path, val);
                }
                catch (NameNotFoundException e)
                {
                    out.printf("lookup(\"%s%s\") = NameNotFound: %s%n", prefix, path, e.getMessage());
                }
            }
        }
    }

    @Test
    public void testLookup() throws NamingException, IOException
    {
        System.out.println("-- Dump from WebApp Scope");
        HttpURLConnection http = (HttpURLConnection)server.getURI().resolve("/jndi-dump").toURL().openConnection();
        try (InputStream in = http.getInputStream())
        {
            String body = IO.toString(in, StandardCharsets.UTF_8);
            System.out.println(body);
        }

        System.out.println("-- Dump from Test scope");
        dumpJndi(System.out);
    }
}

src/main/webroot/jndi-root/WEB-INF/web.xml

的内容
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
         http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
  version="3.1">

  <resource-ref>
    <description>My Foo Resource</description>
    <res-ref-name>val/foo</res-ref-name>
    <res-type>java.lang.Integer</res-type>
    <res-auth>Container</res-auth>
  </resource-ref>
</web-app>

输出看起来像...

2021-10-26 17:05:16.834:INFO:oejs.Server:main: jetty-9.4.44.v20210927; built: 2021-06-30T11:07:22.254Z; git: 526006ecfa3af7f1a27ef3a288e2bef7ea9dd7e8; jvm 11.0.12+7
2021-10-26 17:05:17.012:INFO:oejsh.ContextHandler:main: Started o.e.j.w.WebAppContext@19932c16{/,file:///home/joakim/code/jetty/junk/src/main/webroots/jndi-root/,AVAILABLE}
2021-10-26 17:05:17.033:INFO:oejs.AbstractConnector:main: Started ServerConnector@212b5695{HTTP/1.1, (http/1.1)}{0.0.0.0:45387}
2021-10-26 17:05:17.034:INFO:oejs.Server:main: Started @816ms
-- Dump from WebApp Scope
lookup("java:comp/env/val/foo") = 909
lookup("java:comp/env/entry/foo") = 660
lookup("val/foo") = 707
lookup("entry/foo") = 440

-- Dump from Test scope
lookup("java:comp/env/val/foo") = NameNotFound: env is not bound
lookup("java:comp/env/entry/foo") = NameNotFound: env is not bound
lookup("val/foo") = 707
lookup("entry/foo") = 440
2021-10-26 17:05:17.209:INFO:oejs.AbstractConnector:main: Stopped ServerConnector@212b5695{HTTP/1.1, (http/1.1)}{0.0.0.0:0}
2021-10-26 17:05:17.210:INFO:oejs.session:main: node0 Stopped scavenging
2021-10-26 17:05:17.214:INFO:oejsh.ContextHandler:main: Stopped o.e.j.w.WebAppContext@19932c16{/,file:///home/joakim/code/jetty/junk/src/main/webroots/jndi-root/,STOPPED}

希望上面的范围差异很明显。

Eclipse Jetty Embedded Cookbook 项目现在提供了上述测试的变体。

https://github.com/jetty-project/embedded-jetty-cookbook/

提供 3 种不同的 Jetty 风格