随笔 - 394  文章 - 0  评论 - 946  阅读 - 143万 

上篇我们主要讲解利用Jersey组件如何来写一个能保证基本运行的Rest Service, 之所以说能够基本运行是因为接口暴露及其简易,一旦遇到其他的情况了,就无法正确的处理我们的请求。同时,这个接口返回内容太简单了,如果调用失败,调用者根本无法准确的知道具体的错误信息。那么这节,我们将完善接口,为调用者提供 400-Bad Request, 500-Server Error, 304-Not Modified, 200-Response OK, 404-Not Found的识别标志,让调用者能够明白是什么错误,错在了什么地方。

返回码概览

1.400-Bad Request: 这种错误码一般是请求错误,比如说我的UserID需要传入UUID类型的,但是我传入了不符合要求的数据,比如说Int类型,那么服务端收到这种请求,可以直接给客户端返回这个错误代码并附加上相应的错误说明,那么客户端就能明白错在什么地方了。

2.500-Bad Request: 这种错误码一般是处理错误,也就是对整个处理管道异常处理的捕捉,我们可以在catch里面抛出这种错误给用户并附加上相应的错误代码。

3.404-Not Found: 这种错误码一般是找不到内容所致。也就是说如果请求方发给的请求,但是在数据库中找不到相关信息,则可以返回这种错误。

4.200-Response OK: 当客户端发起请求,服务端成功响应,则可以发送这种状态码。

5.304-Not Modified: 这种返回码是通过计算ETag来进行的,具体的流程如下: 客户端首先向服务器端发送请求,服务器端收到请求,然后计算出Etag,附加到header中传递给客户端。客户端以后再请求的话,需要附带上 If-None-Match:Etag值,发送给服务器端,服务器接收到这个值后,然后重新生成Etag值,最后做比对,如果没变,则返回客户端304;如果有变化,则需要从数据库提取数据,返回给客户端200状态码。

代码设计

在这里我们不在详细说明具体的设计步骤,我只展示具体的代码。

首先,我们定义一个Get的API操作方法:

1
2
3
4
5
6
7
/**
 * 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处理:

1
2
3
4
@Override
public List<UserLearningCourse> findLearnerProgressSummaryByTitanUserIdent(String titanUserId) {
    return userLearningCourseDao.fetchSummaryProgressByUserIdent(titanUserId);
}

是不是很简单,数据库处理这块直接从数据库获取数据集合,返回即可。

然后,我们来看看业务Bean处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
@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附加给客户端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@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状态码返回:

image

然后我们看看Header的内容:

image

很清晰的看到了ETag的返回值。

2.用户ID不是UUID类型的时候,400状态码返回:

image

3.用户ID在数据库中不存在的时候,由于查询不到数据,404状态码返回:

image

4.用户附加ETag,请求相同的数据的时候,由于服务器之前返回过一致的内容了,所以这里不必再返回,直接返回304状态码提示数据未更新:

image

5.用户附加错误的ETag(未加双引号),导致服务端解析出错,直接返回500状态码提示错误:

image

posted on   程序诗人  阅读(2593)  评论(1编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示