上篇咱们主要讲解利用Jersey组件如何来写一个能保证基本运行的Rest Service, 之因此说可以基本运行是由于接口暴露及其简易,一旦遇到其余的状况了,就没法正确的处理咱们的请求。同时,这个接口返回内容太简单了,若是调用失败,调用者根本没法准确的知道具体的错误信息。那么这节,咱们将完善接口,为调用者提供 400-Bad Request, 500-Server Error, 304-Not Modified, 200-Response OK, 404-Not Found的识别标志,让调用者可以明白是什么错误,错在了什么地方。html
返回码概览java
1.400-Bad Request: 这种错误码通常是请求错误,好比说个人UserID须要传入UUID类型的,可是我传入了不符合要求的数据,好比说Int类型,那么服务端收到这种请求,能够直接给客户端返回这个错误代码并附加上相应的错误说明,那么客户端就能明白错在什么地方了。数据库
2.500-Bad Request: 这种错误码通常是处理错误,也就是对整个处理管道异常处理的捕捉,咱们能够在catch里面抛出这种错误给用户并附加上相应的错误代码。服务器
3.404-Not Found: 这种错误码通常是找不到内容所致。也就是说若是请求方发给的请求,可是在数据库中找不到相关信息,则能够返回这种错误。app
4.200-Response OK: 当客户端发起请求,服务端成功响应,则能够发送这种状态码。less
5.304-Not Modified: 这种返回码是经过计算ETag来进行的,具体的流程以下: 客户端首先向服务器端发送请求,服务器端收到请求,而后计算出Etag,附加到header中传递给客户端。客户端之后再请求的话,须要附带上 If-None-Match:Etag值,发送给服务器端,服务器接收到这个值后,而后从新生成Etag值,最后作比对,若是没变,则返回客户端304;若是有变化,则须要从数据库提取数据,返回给客户端200状态码。ide
代码设计fetch
在这里咱们不在详细说明具体的设计步骤,我只展现具体的代码。ui
首先,咱们定义一个Get的API操做方法:lua
/** * Provide the endpoint used to get the summary of the user progress */ @GET @Path("{"+ PtsResourcePaths.PROGRESS_SUMMARY_ROOT_PATH_RESOURCE+"}/"+ PtsResourcePaths.PROGRESS_SUMMARY_BINARY_PATH_RESOURCE) @Produces(MediaType.APPLICATION_JSON) Response UserLearningSummaryRequest(@PathParam(PtsResourcePaths.PROGRESS_SUMMARY_ROOT_PATH_RESOURCE) String titanUserId, @Context Request request);
从上面的定义中,咱们能够看到其接收两个参数,一个是用户ID,另一个是客户端请求上下文(Request对象能够进行ETag值的计算)。
而后,咱们来看看数据库Bean处理:
@Override public List<UserLearningCourse> findLearnerProgressSummaryByTitanUserIdent(String titanUserId) { return userLearningCourseDao.fetchSummaryProgressByUserIdent(titanUserId); }
是否是很简单,数据库处理这块直接从数据库获取数据集合,返回便可。
而后,咱们来看看业务Bean处理:
@Override public GenericResponseWrapperDto<UserLearningCoursesResponse> processUserLearningSummaryRequest(String titanUserId, Request request) throws DataValidationException { GenericResponseWrapperDto<UserLearningCoursesResponse> responseWrapper = new GenericResponseWrapperDto<UserLearningCoursesResponse>(); UserLearningCoursesResponseDto userLearningCoursesResponseDto = new UserLearningCoursesResponseDto(); List userLearningSummaryDtoCollection = new ArrayList(); //if the titanUserId match the uuid format if (!titanUserId.matches("[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}")) { responseWrapper.setHttpStatusCode(PtsErrors.ErrorEnum.TITAN_USER_IDENT_INVALID.getStatus().getStatusCode()); responseWrapper.setErrorMessage(PtsErrors.ErrorEnum.TITAN_USER_IDENT_INVALID.getErrorMessage()); responseWrapper.setErrorCode(PtsErrors.ErrorEnum.TITAN_USER_IDENT_INVALID.getErrorCode()); return responseWrapper; } List<UserLearningCourse> resultCollection = userLearningSummaryBean.findLearnerProgressSummaryByTitanUserIdent(titanUserId); if (resultCollection == null || resultCollection.size()==0) { responseWrapper.setHttpStatusCode(PtsErrors.ErrorEnum.USER_LEARNING_COURSE_SUMMARY_EMPTY.getStatus().getStatusCode()); responseWrapper.setErrorMessage(PtsErrors.ErrorEnum.USER_LEARNING_COURSE_SUMMARY_EMPTY.getErrorMessage()); responseWrapper.setErrorCode(PtsErrors.ErrorEnum.USER_LEARNING_COURSE_SUMMARY_EMPTY.getErrorCode()); return responseWrapper; } //mapper between the response entity and responsedto entity String etagStr = ""; for (UserLearningCourse userLearningCourse : resultCollection) { UserLearningCourseResponseDto userLearningCourseResponseDto = new UserLearningCourseResponseDto(); userLearningCourseResponseDto.setCourseId(userLearningCourse.getCourseIdentifier()); userLearningCourseResponseDto.setBestGrade(userLearningCourse.getBestCourseGrade()); userLearningCourseResponseDto.setLatestGrade(userLearningCourse.getLatestCourseGrade()); List<UserLearningSequence> userLearningSequences = userLearningCourse.getUserLearningSequences(); List<UserLearningSequenceResponse> userLearningSequenceResponses = new ArrayList<UserLearningSequenceResponse>(); if (userLearningSequences != null && userLearningSequences.size() > 0) { for (UserLearningSequence userLearningSequence : userLearningSequences) { UserLearningSequenceResponseDto userLearningSequenceResponse = new UserLearningSequenceResponseDto(); userLearningSequenceResponse.setSequenceId(userLearningSequence.getSequenceIdentifier()); userLearningSequenceResponse.setBestGrade(userLearningSequence.getBestSequenceGrade()); userLearningSequenceResponse.setLatestGrade(userLearningSequence.getLatestSequenceGrade()); userLearningSequenceResponses.add(userLearningSequenceResponse); } } userLearningCourseResponseDto.setSequences(userLearningSequenceResponses); userLearningSummaryDtoCollection.add(userLearningCourseResponseDto); //calculate the ETAG etagStr += formatDate(userLearningCourse.getlastModifiedAt()); } userLearningCoursesResponseDto.setUserId(titanUserId); userLearningCoursesResponseDto.setCourses(userLearningSummaryDtoCollection); //the hashcode should based on the last_modify_date in database. EntityTag eTag = new EntityTag(etagStr.hashCode() + ""); //verify if it matched with etag available in http request Response.ResponseBuilder builder = request.evaluatePreconditions(eTag); //set Etag value setETag(eTag); if (builder != null) { //304 here, Not modified,directly return responseWrapper.setHttpStatusCode(Response.Status.NOT_MODIFIED.getStatusCode()); } else { //200 here, Content changed responseWrapper.setHttpStatusCode(Response.Status.OK.getStatusCode()); responseWrapper.setPayload(userLearningCoursesResponseDto); } return responseWrapper; }
从上面的代码中,咱们能够看到,ETAG的计算是把LastModifiyAt的时间相加,而后经过HashCode方法获得处理结果,而后发送给客户端。客户端再次请求发送Etag过来的时候,咱们能够经过request.evaluatePreconditions(eTag)来计算当前的ETag和客户端发来的ETag是否相等,若是一致则返回304状态码,若是不一致,则代表数据库数据有变化,则直接从数据库从新获取数据,而后发送给客户端。
最后咱们看看服务端如何把ETag附加给客户端:
@Override public Response UserLearningSummaryRequest(String titanUserId,Request request) { GenericResponseWrapper<?> responseWrapper = userLearningSummaryHandlerBean .processUserLearningSummaryRequest(titanUserId, request); ResponseBuilder responseBuilder = Response.status(responseWrapper.getHttpStatusCode()); responseBuilder.entity(responseWrapper); //Send ETag back to client responseBuilder.tag(userLearningSummaryHandlerBean.getETag()); return responseBuilder.build(); }
经过responseBuilder便可返回,responseBuilder是对Jersey的Client对象的封装。
接口调试
1.正常请求,200状态码返回:
而后咱们看看Header的内容:
很清晰的看到了ETag的返回值。
2.用户ID不是UUID类型的时候,400状态码返回:
3.用户ID在数据库中不存在的时候,因为查询不到数据,404状态码返回:
4.用户附加ETag,请求相同的数据的时候,因为服务器以前返回过一致的内容了,因此这里没必要再返回,直接返回304状态码提示数据未更新:
5.用户附加错误的ETag(未加双引号),致使服务端解析出错,直接返回500状态码提示错误: