我找出了让嵌入式 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 代码。你的选择
祝你好运。