【问题标题】:What are the dependencies for getting Jersey 2 to work with embedded Tomcat让 Jersey 2 与嵌入式 Tomcat 一起工作的依赖项是什么
【发布时间】:2019-09-25 10:42:57
【问题描述】:

根据这个问题的答案 - [How to add a Jersey REST webservice into embedded tomcat?

这似乎对其他人有用。有没有人有一个 GIT 项目或一个 Maven 项目所需的依赖项列表,该项目适用于具有嵌入式 tomcat(v8.x 或更高版本)和 Jersey v2.x 的 REST 端点?

这是 POM 文件

http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0

<artifactId>unsprung-tomcat-container</artifactId>
<version>0.1.0-beta</version>
<packaging>jar</packaging>

<properties>
    <tomcat.version>8.5.23</tomcat.version>
    <jersey.version>2.26</jersey.version>

    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
</properties>

<dependencies>
    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
        <version>2.2.4</version>
    </dependency>
    <dependency>
        <groupId>org.glassfish.jersey.inject</groupId>
        <artifactId>jersey-hk2</artifactId>
        <version>${jersey.version}</version>
    </dependency>
    <dependency>
        <groupId>org.apache.tomcat.embed</groupId>
        <artifactId>tomcat-embed-core</artifactId>
        <version>${tomcat.version}</version>
    </dependency>
    <dependency>
        <groupId>org.glassfish.jersey.containers</groupId>
        <artifactId>jersey-container-servlet</artifactId>
        <version>${jersey.version}</version>
    </dependency>

    <dependency>
        <groupId>org.glassfish.jersey.core</groupId>
        <artifactId>jersey-client</artifactId>
        <version>${jersey.version}</version>
        <scope>test</scope>
    </dependency>
</dependencies>

这里是启动tomcat的类:

公共类主{

// constants
private static final String JERSEY_SERVLET_NAME = "jersey-container-servlet";

// Base URI the HTTP server will listen on
public static final String APP_NAME = "api";
public static final String BASE_URI = "http://localhost:8080/"+APP_NAME+"/";

// attributes
private static String baseUrl;

public static Tomcat startServer(boolean useSsl) throws Exception {
    // create server configuration
    final ResourceConfig rc = new ResourceConfig()
                    .packages("cs.toolkit.ms.tomcat")
                    .registerClasses(GsonMessageBodyHandler.class;

    String port = System.getenv("PORT");
    if (port == null || port.isEmpty()) {
        port = "8080";
    }

    String contextPath = "";
    String appBase = ".";

    Tomcat tomcat = new Tomcat();
    tomcat.setPort(Integer.valueOf(port));
    tomcat.getHost().setAppBase(appBase);

    Context context = tomcat.addContext(contextPath, appBase);
    Tomcat.addServlet(context, JERSEY_SERVLET_NAME,
            new ServletContainer(rc));
    context.addServletMappingDecoded("/api/*", JERSEY_SERVLET_NAME);

    tomcat.start();

    return tomcat;
}

}

这里是包含端点的资源类

@Path("/")

公共类 MyResource {

/**
 * Method handling HTTP GET requests.The returned object will be sent 
 * to the client as "text/plain" media type.
 *
 * @param requestType - request type URI
 * @return TestPojo object
 */
@GET
@Path("{requestType}")
@Produces(MediaType.APPLICATION_JSON)
public TestPojo getIt(@PathParam("requestType") String requestType) {
    TestPojo pojo = new TestPojo();
    pojo.setName(requestType);

    return pojo;
}

}

...这里启动服务器后是我用来测试端点的代码

    ClientConfig clientConfig = new ClientConfig();


    Client client = ClientBuilder.newBuilder()
            .withConfig(clientConfig)
            .build();

    client.register(GsonMessageBodyHandler.class);
    target = client.target(Main.BASE_URI);

    String responseMsg = target.path("hello").request().get(String.class);

...这是我得到的错误

javax.ws.rs.NotAuthorizedException: HTTP 401 Unauthorized

【问题讨论】:

  • 请显示您尝试过的内容,minimal reproducible example,其中将包含一个完整的 pom,以及用于启动一个简单服务器的完整代码。谢谢,

标签: rest tomcat embedded jersey-2.0


【解决方案1】:

我找出了让嵌入式 tomcat 实例启动并接受休息端点上的请求所需的依赖项。

pom 文件:

    `<properties>
        <!--using tomcat 8.x-->
        <tomcat.version>8.5.47</tomcat.version>
        <tomcat-logging.version>8.5.2</tomcat-logging.version>
        <jersey-mvc.version>2.28</jersey-mvc.version>
        <json.version>2.8.6</json.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>`

    <dependencies>
        <!--Jersey 2 support-->
        <dependency>
            <groupId>org.glassfish.jersey.ext</groupId>
            <artifactId>jersey-mvc-freemarker</artifactId>
            <version>${jersey-mvc.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>javax.servlet</groupId>
                    <artifactId>servlet-api</artifactId>
                </exclusion>
                <!--jersey-mvc-jsp already has jersey-mvc-->
                <exclusion>
                    <groupId>org.glassfish.jersey.ext</groupId>
                    <artifactId>jersey-mvc</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jersey.inject</groupId>
            <artifactId>jersey-hk2</artifactId>
            <version>${jersey-mvc.version}</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jersey.ext</groupId>
            <artifactId>jersey-mvc-jsp</artifactId>
            <version>${jersey-mvc.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>javax.servlet</groupId>
                    <artifactId>servlet-api</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!-- Dependencies for Embedded Tomcat -->
        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-servlet-api</artifactId>
            <version>${tomcat.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-core</artifactId>
            <version>${tomcat.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-logging-juli</artifactId>
            <version>${tomcat-logging.version}</version>
        </dependency>
        <dependency>
            <!-- required for LifecycleContainer -->
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
            <version>${tomcat.version}</version>
        </dependency>

        <!--JSON support-->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>${json.version}</version>
        </dependency>

        <!--unit testing-->
        <dependency>
            <groupId>org.glassfish.jersey.core</groupId>
            <artifactId>jersey-client</artifactId>
            <version>${jersey-mvc.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-core</artifactId>
            <version>1.3</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

主类

    public class Main {
    // constants
    public static int PORT = 8443;

    public static final String KEYSTORE_SERVER_FILE = "keystore_server";
    public static final String KEYSTORE_SERVER_PWD = "asdfgh";
    public static final String TRUSTORE_SERVER_FILE = "truststore_server";
    public static final String TRUSTORE_SERVER_PWD = "asdfgh";

    // attributes
    private static Tomcat tomcat;

    public static void stop() throws LifecycleException {
        tomcat.stop();
    }

    public static void start(String... args) throws Exception {
        // set system properties for configuring tomcat
        System.setProperty("tomcat.util.scan.StandardJarScanFilter.jarsToSkip", "*.jar"); // skip TLD/JSP scanning

        // create server instance
        tomcat = new Tomcat();

        //set tomcat port form yml configuration file
        tomcat.setPort(PORT);
        System.out.println("Tomcat running on port : " + PORT);

        // set SSL connector
        Connector httpsConnector = getSslConnector();
        tomcat.getService().addConnector(httpsConnector);
        tomcat.setConnector(httpsConnector);

        // create web-app
        File base = new File("src/main/webapp");
        Context context = tomcat.addContext("/api", base.getAbsolutePath());
        tomcat.addWebapp(null, "", base.getAbsolutePath());

        System.out.println("WEB-URI base path: " + base.getAbsolutePath());


        Tomcat.addServlet(context, "jersey-container-servlet", resourceConfig());
        context.addServletMapping("/*", "jersey-container-servlet");

        // start server
        tomcat.start();

    }

    private static Connector getSslConnector() {
        Path ksfPath = FileSystems.getDefault().getPath(KEYSTORE_SERVER_FILE);
        Path tsfPath = FileSystems.getDefault().getPath(TRUSTORE_SERVER_FILE);

        Connector connector = new Connector();
        connector.setPort(PORT);
        connector.setSecure(true);
        connector.setScheme("https");
        connector.setAttribute("keystoreType", "JKS");
        connector.setAttribute("keystorePass", KEYSTORE_SERVER_PWD);
        connector.setAttribute("keystoreFile",
                ksfPath.toFile().getAbsolutePath() );
        connector.setAttribute("trustStorePassword", TRUSTORE_SERVER_PWD);
        connector.setAttribute("trustStoreFile",
                tsfPath.toFile().getAbsolutePath() );
        connector.setAttribute("clientAuth", "false");
        connector.setAttribute("protocol", "HTTP/1.1");
        connector.setAttribute("sslProtocol", "TLSv1.2");
        connector.setAttribute("maxThreads", "200");
        connector.setAttribute("SSLEnabled", true);

        return connector;
     }    

    private static ServletContainer resourceConfig() {
        System.out.println("Scanning package: " + Main.class.getPackage().getName());
        final ResourceConfig config = new ResourceConfig()
                .packages(Main.class.getPackage().getName())
                .registerClasses( GsonMessageBodyHandler.class
                                , SecurityFilter.class
                                , AuthenticationExceptionMapper.class )
                .register(Resource.class)
                .register(MyResource.class)
                ;

        return new ServletContainer(config);
    }

}

带有休息端点的资源类

    @Path("/")
public class MyResource {

    /**
     * Method handling HTTP GET requests.The returned object will be sent 
     * to the client as "text/plain" media type.
     *
     * @param requestType - request type URI
     * @return TestPojo object
     */
    @GET
    @Path("{requestType}")
    @Produces(MediaType.APPLICATION_JSON)
    public TestPojo getIt(@PathParam("requestType") String requestType) {
        TestPojo pojo = new TestPojo();
        pojo.setName(requestType);

        return pojo;
    }
}

JSON 读写器

`@Provider
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)`

    public class GsonMessageBodyHandler implements MessageBodyWriter<Object>, MessageBodyReader<Object> {
    // constants
    private static final String UTF_8 = "UTF-8";

    // attributes
    private Gson gson;

    // support

    //Customize the gson behavior here
    private Gson getGson() {
        if (gson == null) {
            final GsonBuilder gsonBuilder = new GsonBuilder();
            gson = gsonBuilder.disableHtmlEscaping()
                    .setPrettyPrinting()
                    .serializeNulls()
                    .create();
        }
        return gson;
    }

    @Override
    public boolean isReadable(Class<?> type, Type genericType,
            java.lang.annotation.Annotation[] annotations, MediaType mediaType) {
        return true;
    }

    @Override
    public Object readFrom(Class<Object> type, Type genericType,
            Annotation[] annotations, MediaType mediaType,
            MultivaluedMap<String, String> httpHeaders, InputStream entityStream) {
        InputStreamReader streamReader = null;
        try {
            streamReader = new InputStreamReader(entityStream, UTF_8);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        try {
            Type jsonType;
            if (type.equals(genericType)) {
                jsonType = type;
            } else {
                jsonType = genericType;
            }
            return getGson().fromJson(streamReader, jsonType);
        } finally {
            try {
                streamReader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public boolean isWriteable(Class<?> type, Type genericType,
            Annotation[] annotations, MediaType mediaType) {
        return true;
    }

    @Override
    public long getSize(Object object, Class<?> type, Type genericType,
            Annotation[] annotations, MediaType mediaType) {
        return -1;
    }

    @Override
    public void writeTo(Object object, Class<?> type, Type genericType,
            Annotation[] annotations, MediaType mediaType,
            MultivaluedMap<String, Object> httpHeaders,
            OutputStream entityStream) throws IOException,
            WebApplicationException {
        OutputStreamWriter writer = new OutputStreamWriter(entityStream, UTF_8);
        try {
            Type jsonType;
            if (type.equals(genericType)) {
                jsonType = type;
            } else {
                jsonType = genericType;
            }
            getGson().toJson(object, jsonType, writer);
        } finally {
            writer.close();
        }
    }
}

身份验证支持

@Provider
@PreMatching

    public class SecurityFilter implements ContainerRequestFilter {

    @Inject
    javax.inject.Provider<UriInfo> uriInfo;
    private static final String REALM = "HTTPS Example authentication";

    @Override
    public void filter(ContainerRequestContext filterContext) throws IOException {
        User user = authenticate(filterContext);
        filterContext.setSecurityContext(new Authorizer(user));
    }

    private User authenticate(ContainerRequestContext filterContext) {
        // Extract authentication credentials
        String authentication = filterContext.getHeaderString(HttpHeaders.AUTHORIZATION);
        if (authentication == null) {
            throw new AuthenticationException("Authentication credentials are required", REALM);
        }
        if (!authentication.startsWith("Basic ")) {
            return null;
            // additional checks should be done here
            // "Only HTTP Basic authentication is supported"
        }
        authentication = authentication.substring("Basic ".length());
        String[] values = new String(DatatypeConverter.parseBase64Binary(authentication), Charset.forName("ASCII")).split(":");
        if (values.length < 2) {
            throw new WebApplicationException(400);
            // "Invalid syntax for username and password"
        }
        String username = values[0];
        String password = values[1];
        if ((username == null) || (password == null)) {
            throw new WebApplicationException(400);
            // "Missing username or password"
        }

        // Validate the extracted credentials
        User user;

        if (username.equals("user") && password.equals("password")) {
            user = new User("user", "user");
            System.out.println("USER AUTHENTICATED");
        } else {
            System.out.println("USER NOT AUTHENTICATED");
            throw new AuthenticationException("Invalid username or password\r\n", REALM);
        }
        return user;
    }

    public class Authorizer implements SecurityContext {

        private User user;
        private Principal principal;

        public Authorizer(final User user) {
            this.user = user;
            this.principal = new Principal() {

                public String getName() {
                    return user.username;
                }
            };
        }

        public Principal getUserPrincipal() {
            return this.principal;
        }

        public boolean isUserInRole(String role) {
            return (role.equals(user.role));
        }

        public boolean isSecure() {
            return "https".equals(uriInfo.get().getRequestUri().getScheme());
        }

        public String getAuthenticationScheme() {
            return SecurityContext.BASIC_AUTH;
        }
    }

    public class User {

        public String username;
        public String role;

        public User(String username, String role) {
            this.username = username;
            this.role = role;
        }
    }
}

异常处理:

@Provider

    public class AuthenticationExceptionMapper implements ExceptionMapper<AuthenticationException> {

    @Override
    public Response toResponse(AuthenticationException e) {
        if (e.getRealm() != null) {
            return Response
                    .status(Status.UNAUTHORIZED)
                    .header("WWW-Authenticate", "Basic realm=\"" + e.getRealm() + "\"")
                    .type("text/plain")
                    .entity(e.getMessage())
                    .build();
        } else {
            return Response
                    .status(Status.UNAUTHORIZED)
                    .type("text/plain")
                    .entity(e.getMessage())
                    .build();
        }
    }

}

注意:Exception 类没有什么特别之处。

请求和响应中的POJO

    public class TestPojo {
    private String name = "TEST";
    private int id = 0x3;

    // ctor
    public TestPojo() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }


}

最后是测试类

    public class ResourceTest {
    // constants
    public static final String TRUSTORE_CLIENT_FILE = "./truststore_client";
    public static final String TRUSTSTORE_CLIENT_PWD = "asdfgh";
    public static final String KEYSTORE_CLIENT_FILE = "./keystore_client";
    public static final String KEYSTORE_CLIENT_PWD = "asdfgh";

    public ResourceTest() {
    }

    @BeforeClass
    public static void setUpClass() throws Exception {
        Main.start(new String[1]);
    }

    @AfterClass
    public static void tearDownClass() throws LifecycleException {
        Main.stop();
    }

    @Before
    public void setUp() {
    }

    @After
    public void tearDown() {
    }

    /**
     * Test of get method, of class Resource.
     * @throws java.lang.InterruptedException
     */
    @Ignore
    public void testGet() throws InterruptedException {
        System.out.println("get");

        synchronized(this) {
            this.wait(30000);
        }

        // TODO review the generated test code and remove the default call to fail.
        fail("The test case is a prototype.");
    }

    /**
     * Test of getUser method, of class Resource.
     */
    @Test
    public void testGetTestPojoWithAuth() {
        System.out.println("get TestPojo with basic authentication");

        SslConfigurator sslConfig = SslConfigurator.newInstance()
                .securityProtocol("TLSv1.2")
                .trustStoreFile(TRUSTORE_CLIENT_FILE)
                .trustStorePassword(TRUSTSTORE_CLIENT_PWD)
                .keyStoreFile(KEYSTORE_CLIENT_FILE)
                .keyPassword(KEYSTORE_CLIENT_PWD);

        final SSLContext sslContext = sslConfig.createSSLContext();
        Client client = ClientBuilder.newBuilder()
                                    .register(HttpAuthenticationFeature.basic("user", "password"))
                                    .sslContext(sslContext)
                                    .register(GsonMessageBodyHandler.class)
                                    .build();

        client.property(ClientProperties.CONNECT_TIMEOUT, 1000);
        client.property(ClientProperties.READ_TIMEOUT, 10000);

        String requestType = "test-type";
        TestPojo responseMsg = client.target( "https://localhost:"+Main.PORT+"/api/")
                                    .path("/" + requestType)
                                    .request()
                                    .get()
                                    .readEntity(TestPojo.class);

        assertEquals(requestType, responseMsg.getName());
    }

    /**
     * Test of getUser method, of class Resource.
     */
    @Test
    public void testGetTestPojoWithInvalidAuth() {
        System.out.println("get TestPojo with invalid authentication");

        SslConfigurator sslConfig = SslConfigurator.newInstance()
                .securityProtocol("TLSv1.2")
                .trustStoreFile(TRUSTORE_CLIENT_FILE)
                .trustStorePassword(TRUSTSTORE_CLIENT_PWD)
                .keyStoreFile(KEYSTORE_CLIENT_FILE)
                .keyPassword(KEYSTORE_CLIENT_PWD);

        final SSLContext sslContext = sslConfig.createSSLContext();
        Client client = ClientBuilder.newBuilder()
                                    .register(HttpAuthenticationFeature.basic("bad-user", "bad-password"))
                                    .sslContext(sslContext)
                                    .register(GsonMessageBodyHandler.class)
                                    .build();

        client.property(ClientProperties.CONNECT_TIMEOUT, 1000);
        client.property(ClientProperties.READ_TIMEOUT, 10000);

        String requestType = "test-type";
        Response response = client.target( "https://localhost:"+Main.PORT+"/api/")
                                    .path("/" + requestType)
                                    .request()
                                    .get();

        assertEquals(401, response.getStatus());
    }

}

请注意以下几点:

所有类都在应用程序项目的同一个包中。这允许我在设置 ResouceConfig.packages(...) 方法时只使用 Main.class 包名称。

您需要在项目的 /src/main 下创建一个“webapp”文件夹,否则容器可能无法正常启动。它是一个遗留的 tomcat 东西。

您正在创建自己的证书... ;) 或者您可以只注释掉 SSL 代码。你的选择

祝你好运。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-05-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-07
    相关资源
    最近更新 更多