上篇咱們主要講解利用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狀態碼提示錯誤: