jersey客户端_项目学生:带有Jersey的Web服务客户端
jersey客戶端
這是Project Student的一部分。 其他職位包括帶有Jersey的Webservice Client, 帶有Spring Data的 業(yè)務(wù)層和持久性 。
RESTful Web應(yīng)用程序洋蔥的第一層是Web服務(wù)客戶端。 它可以用于模仿包含AJAX內(nèi)容的網(wǎng)頁(yè),也可以被webapp的編程用戶用來(lái)模仿。 注意,后者可能包括其他Web應(yīng)用程序,例如,如果您的內(nèi)部RESTful服務(wù)器被許多創(chuàng)建常規(guī)網(wǎng)頁(yè)的演示服務(wù)器包圍著。
設(shè)計(jì)決策
澤西島 -我使用澤西島庫(kù)進(jìn)行REST調(diào)用。 我考慮了幾種選擇,但決定選擇Jersey,因?yàn)樗禽p量級(jí)的,不會(huì)對(duì)開(kāi)發(fā)人員施加太多限制。 相比之下,例如,使用Spring庫(kù)除了引入其他庫(kù)外,還可能在EJB3環(huán)境中引起問(wèn)題。
UUID –數(shù)據(jù)庫(kù)將使用整數(shù)主鍵,但Web服務(wù)將使用UUID標(biāo)識(shí)值。 這是出于安全考慮–如果攻擊者知道帳戶ID 1008存在,那么可以肯定地說(shuō)帳戶ID 1007存在。 更重要的是,用戶ID 0可能存在并且具有比普通用戶更多的特權(quán)。
UUID并非如此-在大多數(shù)情況下,知道一個(gè)UUID并不能洞悉其他UUID。 這不是100%準(zhǔn)確的-一些UUID由IP地址或時(shí)間戳組成,因此,知識(shí)淵博的攻擊者可以極大地減少可能的值范圍-但是目前隨機(jī)UUID足夠“好”。
局限性
我采用的是“實(shí)現(xiàn)所需的最少功能”方法,因此初始實(shí)現(xiàn)存在許多限制。
身份驗(yàn)證 –沒(méi)有嘗試提供身份驗(yàn)證信息。
加密 –沒(méi)有嘗試加密Web服務(wù)調(diào)用。
僅CRUD方法 –僅支持基本CRUD方法。
請(qǐng)記住-限制很好, 但是必須清楚地記錄在案 。 在最好的情況下,它們將被添加到敏捷積壓中。
客戶端API
客戶端API是基本的CRUD。 我們稍后可以添加功能。
public interface CourseRestClient {/*** Get list of all courses.*/Course[] getAllCourses();/*** Get details for specific course.* @param uuid*/Course getCourse(String uuid);/*** Create specific course.* @param name*/Course createCourse(String name);/*** Update specific course.* @param uuid* @param name*/Course updateCourse(String uuid, String name);/*** Delete course.* @param uuid*/void deleteCourse(String uuid); }例外情況
該API包括三個(gè)運(yùn)行時(shí)異常。 第一個(gè)是RestClientException,它是抽象的運(yùn)行時(shí)異常,它是所有其他異常的基類。
缺少期望值時(shí)將引發(fā)ObjectNotFoundException。 (實(shí)施說(shuō)明:這是由404狀態(tài)代碼觸發(fā)的。)此異常包含足夠的信息以唯一地標(biāo)識(shí)期望的對(duì)象。
public class ObjectNotFoundException extends RestClientException {private static final long serialVersionUID = 1L;private final String resource;private final Class<? extends PersistentObject> objectClass;private final String uuid;public ObjectNotFoundException(final String resource,final Class<? extends PersistentObject> objectClass,final String uuid) {super("object not found: " + resource + "[" + uuid + "]");this.resource = resource;this.objectClass = objectClass;this.uuid = uuid;}public String getResource() {return resource;}public Class<? extends PersistentObject> getObjectClass() {return objectClass;}public String getUuid() {return uuid;} }RestClientFailureException是用于意外或未處理狀態(tài)代碼的通用處理程序。
public class RestClientFailureException extends RestClientException {private static final long serialVersionUID = 1L;private final String resource;private final Class<? extends PersistentObject> objectClass;private final String uuid;private final int statusCode;/*** Constructor* * @param resource* @param objectClass* @param uuid* @param response*/public RestClientFailureException(final String resource,final Class<? extends PersistentObject> objectClass,final String uuid, final ClientResponse response) {super("rest client received error: " + resource + "[" + uuid + "]");this.resource = resource;this.objectClass = objectClass;this.uuid = uuid;this.statusCode = response.getStatus();}public String getResource() {return resource;}public Class<? extends PersistentObject> getObjectClass() {return objectClass;}/*** Get UUID, "<none>" (during listAllX()) or "(name)" (during createX())* * @return*/public String getUuid() {return uuid;}/*** Get standard HTTP status code.* * @return*/public int getStatusCode() {return statusCode;} }添加客戶端身份驗(yàn)證后,我們希望添加UnauthorizedOperationException。
客戶實(shí)施
基本的CRUD實(shí)現(xiàn)通常是樣板,因此我們可以使用抽象類來(lái)完成大部分繁重的工作。 更多高級(jí)功能可能需要此類來(lái)直接進(jìn)行Jersey呼叫。
/*** This is the Course-specific implementation.*/ public class CourseRestClientImpl extends AbstractRestClientImpl<Course>implements CourseRestClient {private static final Course[] EMPTY_COURSE_ARRAY = new Course[0];/*** Constructor.* * @param courseResource*/public CourseRestClientImpl(final String resource) {super(resource, Course.class, Course[].class);}/*** Create JSON string.* * @param name* @return*/String createJson(final String name) {return String.format("{ \"name\": \"%s\" }", name);}/*** @see com.invariantproperties.sandbox.student.webservice.client.CourseRestClient#getAllCourses()*/public Course[] getAllCourses() {return super.getAllObjects(EMPTY_COURSE_ARRAY);}/*** @see com.invariantproperties.sandbox.student.webservice.client.CourseRestClient#getCourse(java.lang.String)*/public Course getCourse(final String uuid) {return super.getObject(uuid);}/*** @see com.invariantproperties.sandbox.student.webservice.client.CourseRestClient#createCourse(java.lang.String)*/public Course createCourse(final String name) {if (name == null || name.isEmpty()) {throw new IllegalArgumentException("'name' is required");}return createObject(createJson(name));}/*** @see com.invariantproperties.sandbox.student.webservice.client.CourseRestClient#updateCourse(java.lang.String,* java.lang.String)*/public Course updateCourse(final String uuid, final String name) {if (name == null || name.isEmpty()) {throw new IllegalArgumentException("'name' is required");}return super.updateObject(createJson(name), uuid);}/*** @see com.invariantproperties.sandbox.student.webservice.client.CourseRestClient#deleteCourse(java.lang.String)*/public void deleteCourse(final String uuid) {super.deleteObject(uuid);} }抽象基類進(jìn)行繁重的工作。
public class AbstractRestClientImpl<T extends PersistentObject> {private final String resource;private final Class<T> objectClass;private final Class<T[]> objectArrayClass;/*** Constructor.* * @param resource*/public AbstractRestClientImpl(final String resource,final Class<T> objectClass, final Class<T[]> objectArrayClass) {this.resource = resource;this.objectClass = objectClass;this.objectArrayClass = objectArrayClass;}/*** Helper method for testing.* * @return*/Client createClient() {return Client.create();}/*** List all objects. This is a risky method since there's no attempt at* pagination.*/public T[] getAllObjects(final T[] emptyListClass) {final Client client = createClient();try {final WebResource webResource = client.resource(resource);final ClientResponse response = webResource.accept(MediaType.APPLICATION_JSON).get(ClientResponse.class);if (response.getStatus() == Response.Status.OK.getStatusCode()) {T[] entities = response.getEntity(objectArrayClass);return entities;} else {throw new RestClientFailureException(resource, objectClass,"<none>", response);}} finally {client.destroy();}}/*** Get a specific object.*/public T getObject(String uuid) {final Client client = createClient();try {final WebResource webResource = client.resource(resource + uuid);final ClientResponse response = webResource.accept(MediaType.APPLICATION_JSON).get(ClientResponse.class);if (response.getStatus() == Response.Status.OK.getStatusCode()) {final T entity = response.getEntity(objectClass);return entity;} else if (response.getStatus() == Response.Status.NOT_FOUND.getStatusCode()) {throw new ObjectNotFoundException(resource, objectClass, uuid);} else {throw new RestClientFailureException(resource, objectClass,uuid, response);}} finally {client.destroy();}}/*** Create an object with the specified values.*/public T createObject(final String json) {final Client client = createClient();try {final WebResource webResource = client.resource(resource);final ClientResponse response = webResource.type(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON).post(ClientResponse.class, json);if (response.getStatus() == Response.Status.CREATED.getStatusCode()) {final T entity = response.getEntity(objectClass);return entity;} else {throw new RestClientFailureException(resource, objectClass, "("+ json + ")", response);}} finally {client.destroy();}}/*** Update an object with the specified json.*/public T updateObject(final String json, final String uuid) {final Client client = createClient();try {final WebResource webResource = client.resource(resource + uuid);final ClientResponse response = webResource.type(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON).post(ClientResponse.class, json);if (response.getStatus() == Response.Status.OK.getStatusCode()) {final T entity = response.getEntity(objectClass);return entity;} else if (response.getStatus() == Response.Status.NOT_FOUND.getStatusCode()) {throw new ObjectNotFoundException(resource, objectClass, uuid);} else {throw new RestClientFailureException(resource, objectClass,uuid, response);}} finally {client.destroy();}}/*** Delete specified object.*/public void deleteObject(String uuid) {final Client client = createClient();try {final WebResource webResource = client.resource(resource + uuid);final ClientResponse response = webResource.accept(MediaType.APPLICATION_JSON).delete(ClientResponse.class);if (response.getStatus() == Response.Status.GONE.getStatusCode()) {// do nothing} else if (response.getStatus() == Response.Status.NOT_FOUND.getStatusCode()) {// do nothing - delete is idempotent} else {throw new RestClientFailureException(resource, objectClass,uuid, response);}} finally {client.destroy();}} }單元測(cè)試
現(xiàn)在我們來(lái)看看我們的測(cè)試代碼。 重要提示:我們要測(cè)試代碼的行為,而不是代碼的實(shí)現(xiàn)。
public class CourseRestClientImplTest {private static final String UUID = "uuid";private static final String NAME = "name";@Testpublic void testGetAllCoursesEmpty() {CourseRestClient client = new CourseRestClientMock(200, new Course[0]);Course[] results = client.getAllCourses();assertEquals(0, results.length);}@Testpublic void testGetAllCoursesNonEmpty() {Course course = new Course();course.setUuid(UUID);CourseRestClient client = new CourseRestClientMock(200,new Course[] { course });Course[] results = client.getAllCourses();assertEquals(1, results.length);}@Test(expected = RestClientFailureException.class)public void testGetAllCoursesError() {CourseRestClient client = new CourseRestClientMock(500, null);client.getAllCourses();}@Testpublic void testGetCourse() {Course course = new Course();course.setUuid(UUID);CourseRestClient client = new CourseRestClientMock(200, course);Course results = client.getCourse(course.getUuid());assertEquals(course.getUuid(), results.getUuid());}@Test(expected = ObjectNotFoundException.class)public void testGetCourseMissing() {CourseRestClient client = new CourseRestClientMock(404, null);client.getCourse(UUID);}@Test(expected = RestClientFailureException.class)public void testGetCourseError() {CourseRestClient client = new CourseRestClientMock(500, null);client.getCourse(UUID);}@Testpublic void testCreateCourse() {Course course = new Course();course.setName(NAME);CourseRestClient client = new CourseRestClientMock(Response.Status.CREATED.getStatusCode(), course);Course results = client.createCourse(course.getName());assertEquals(course.getName(), results.getName());}@Test(expected = RestClientFailureException.class)public void testCreateCourseError() {CourseRestClient client = new CourseRestClientMock(500, null);client.createCourse(UUID);}@Testpublic void testUpdateCourse() {Course course = new Course();course.setUuid(UUID);course.setName(NAME);CourseRestClient client = new CourseRestClientMock(200, course);Course results = client.updateCourse(course.getUuid(), course.getName());assertEquals(course.getUuid(), results.getUuid());assertEquals(course.getName(), results.getName());}@Test(expected = ObjectNotFoundException.class)public void testUpdateCourseMissing() {CourseRestClient client = new CourseRestClientMock(404, null);client.updateCourse(UUID, NAME);}@Test(expected = RestClientFailureException.class)public void testUpdateCourseError() {CourseRestClient client = new CourseRestClientMock(500, null);client.updateCourse(UUID, NAME);}@Testpublic void testDeleteCourse() {Course course = new Course();course.setUuid(UUID);CourseRestClient client = new CourseRestClientMock(Response.Status.GONE.getStatusCode(), null);client.deleteCourse(course.getUuid());}@Testpublic void testDeleteCourseMissing() {CourseRestClient client = new CourseRestClientMock(404, null);client.deleteCourse(UUID);}@Test(expected = RestClientFailureException.class)public void testDeleteCourseError() {CourseRestClient client = new CourseRestClientMock(500, null);client.deleteCourse(UUID);} }最后,我們需要使用模擬的REST客戶端創(chuàng)建一個(gè)要測(cè)試的對(duì)象。 由于Client.createClient是靜態(tài)方法,因此無(wú)法使用依賴項(xiàng)注入,但是我們已將該調(diào)用包裝在可以覆蓋的package-private方法中。 該方法將創(chuàng)建一個(gè)模擬的客戶端,該客戶端提供Jersey庫(kù)中所需的其余值。
class CourseRestClientMock extends CourseRestClientImpl {static final String RESOURCE = "test://rest/course/";private Client client;private WebResource webResource;private WebResource.Builder webResourceBuilder;private ClientResponse response;private final int status;private final Object results;CourseRestClientMock(int status, Object results) {super(RESOURCE);this.status = status;this.results = results;}/*** Override createClient() so it returns mocked object. These expectations* will handle basic CRUD operations, more advanced functionality will* require inspecting JSON payload of POST call.*/Client createClient() {client = Mockito.mock(Client.class);webResource = Mockito.mock(WebResource.class);webResourceBuilder = Mockito.mock(WebResource.Builder.class);response = Mockito.mock(ClientResponse.class);when(client.resource(any(String.class))).thenReturn(webResource);when(webResource.accept(any(String.class))).thenReturn(webResourceBuilder);when(webResource.type(any(String.class))).thenReturn(webResourceBuilder);when(webResourceBuilder.accept(any(String.class))).thenReturn(webResourceBuilder);when(webResourceBuilder.type(any(String.class))).thenReturn(webResourceBuilder);when(webResourceBuilder.get(eq(ClientResponse.class))).thenReturn(response);when(webResourceBuilder.post(eq(ClientResponse.class),any(String.class))).thenReturn(response);when(webResourceBuilder.put(eq(ClientResponse.class),any(String.class))).thenReturn(response);when(webResourceBuilder.delete(eq(ClientResponse.class))).thenReturn(response);when(response.getStatus()).thenReturn(status);when(response.getEntity(any(Class.class))).thenReturn(results);return client;} }整合測(cè)試
這是洋蔥的最外層,因此沒(méi)有有意義的集成測(cè)試。
源代碼
- 可從http://code.google.com/p/invariant-properties-blog/source/browse/student/student-webservices/student-ws-client獲取源代碼。
翻譯自: https://www.javacodegeeks.com/2013/12/project-student-webservice-client-with-jersey.html
jersey客戶端
總結(jié)
以上是生活随笔為你收集整理的jersey客户端_项目学生:带有Jersey的Web服务客户端的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 如何使用Hibernate将Postgr
- 下一篇: linux默认目录的名称及作用(linu