# WSHttpHelper **Repository Path**: scloud_admin/WSHttpHelper ## Basic Information - **Project Name**: WSHttpHelper - **Description**: HttpHelper是一个轻量级Http请求框架,致力于为Http请求提供简洁明了的接口,使开发人员更专注于业务,提高效率。 允许开发人员针对REST接口定义客户端对应的请求Interface类,只需要根据接口定义作出对应配置即可实现类似于RPC的声明式调用,可使请求接口简洁,规范,高效,更有可阅读性。请求HTML资源,可以使用WebClient不仅支持JS渲染执行Ajax请求,还提供了一套数据抽取功能,支持xpath,css,regex任意配置到数据字段的规则抽取,可将文本数据抽取为复杂的(嵌套的)格式化的对象数据。一、设计考虑代码的复用,模块的复用,更考虑了模式的复用;请求的Pipeline是针对模版定义,不是针对单一接口,这种约定可以在准备开发新的处理器时需要考虑从更高层的模版共性出发,作出更通用和公用的处理器。同时模版配置TempleConfig支持继承关系,可以先定义通用的顶级父Temple配置,使用继承和泛化思想实现个性化配置。 二、使用规范和约定将针对接口请求的问题转换成根据业务接口的Client定义,仅需Copy接口定义输入输出POJO即可完成一个接口请求的定义。 - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 127 - **Created**: 2023-06-07 - **Last Updated**: 2023-06-07 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Http Helper [![License](http://img.shields.io/:license-apache-blue.svg "2.0")](http://www.apache.org/licenses/LICENSE-2.0.html) [![JDK 1.8](https://img.shields.io/badge/JDK-1.8-green.svg "JDK 1.8")]() [![star](https://gitee.com/wolfsmoke/WSHttpHelper/badge/star.svg?theme=dark)](https://gitee.com/wolfsmoke/WSHttpHelper/stargazers) [![fork](https://gitee.com/wolfsmoke/WSHttpHelper/badge/fork.svg?theme=dark)](https://gitee.com/wolfsmoke/WSHttpHelper/members) HttpHelper是一个轻量级Http请求框架,致力于为Http请求提供简洁明了的接口,使开发人员更专注于业务,提高效率。v2.0版本提供了一套Http请求处理流程和规范,能够在请求过程的Pipeline中添加丰富的功能处理。 允许开发人员针对REST接口定义客户端对应的请求`Interface`类,只需要根据接口定义作出对应配置即可实现类似于`RPC`的声明式调用,可使请求接口简洁,规范,高效,更有可阅读性。 对于请求`HTML`资源,可以使用`WebClient`不仅支持JS渲染执行`Ajax`请求,还提供了一套数据抽取功能,支持xpath,css,regex任意配置到数据字段的规则抽取,可将文本数据抽取为复杂的(嵌套的)格式化的对象数据。 v2.0不仅提供了更加灵活的扩展机制、更多的内置功能、更快的效率;更采用了全新的设计思想。 设计考虑代码的复用,模块的复用,更考虑了模式的复用;请求的Pipeline是针对模版定义,不是针对单一接口,这种约定可以在准备开发新的处理器时需要考虑从更高层的模版共性出发,作出更通用和公用的处理器。 同时模版配置`TempleConfig`支持继承关系,可以先定义通用的顶级父Temple配置,使用继承和泛化思想实现个性化配置。 使用规范和约定将针对接口请求的问题转换成根据业务接口的Client定义,仅需Copy接口定义输入输出POJO即可完成一个接口请求的定义。 当前Pipeline中内置的Handler能够满足绝大部分的在JAVA中使用http请求的场景。 > PS:最近花了点时间重构了HttpHelper,可以适配SpringBoot,同时使用全新思路解决方案。(文档逐步完善中) > > 另外请关注我的另外一个开源项目(推荐项目)《一个分布式任务分发框架核心包》https://gitee.com/wolfsmoke/ws-task ### v2.0提供功能 * HttpClient支持OkHttpClient和WebClient * OkHttpClient:主要适用于接口调用,轻量级,效率高; * WebClient:主要适用于请求Html网页,能够执行js渲染,模拟浏览器。 * 请求支持同步也异步 * 同步:整个请求到响应过程处于阻塞状态,若需要并行操作需要在应用程序中自行控制多线程处理; * 异步:请求前处理是同步处理,提交Client请求和响应处理是异步处理,异步线程池线程个数配置是Client.threadNumber。 * Pipeline支持请求前处理(实现`RequestHandler`)和请求后处理(实现`ResponseHandler`) * Pipeline在Template.RequestConfig中进行定义,对于同一类请求具有相同的前后处理过程; * Pipeline中Handle的执行顺序由`@HandlerOrder`和添加的顺序共同决定; * `RequestHandler`:从head向后处理,level=SYSTEM优先,value越小越优先,添加顺序靠前优先; * `ResponseHandler`:从tail向前处理,level=SYSTEM优先,value越大越优先,添加顺序靠后优先; * Pipeline是一个包装了Handle的Context的双向链表。 * 支持spring-boot-starter: * 引入`http-helper-spring`使用`@EnableHttpHelper`开启功能 * 创建方式 * Builder方式:使用Builder灵活的创建自定义的HttpHelper对象。 * Annotation方式:在接口上使用`@HttpOperation`注解,即可通过Proxy动态创建实例,适用于接口访问,可实现类似于@Feign的伪RPC声明式调用。具体说明见后续详细说明。 * Properties方式:在SpringBoot项目中通过在Properties中配置`http-helper`属性可以通过spring-boot-starter自动生成对象。 * 请求后解析支持以下类型 * JSON:对于响应类型为`application/json`的数据,通过在Pipeline添加`ParseJsonHandler`即可将数据解析为对象或Map。 * XML:对于响应类型为`application/xml`的数据,通过在Pipeline添加`ParseXmlHandler`即可将数据解析为对象或Map。(中间使用了JSON作为转换) * HTML:对于响应类型为`text/html`的数据,通过在Pipeline添加`ParseXmlHandler`和配置`ParseField`可从HTML中提取数据,并转换为对象或Map。 * 每个`ParseField`支持String,List,Object类型。其中List的项和Object通过配置childField可以定义为一个复杂对象。 * 每个`ParseField`支持从HTML中抽取数据的表达式支持CSS(类似JQuery),XPATH,REGEX,可以适用于绝大多数数据抽取场景。 * byte[]:对应响应类型为byte[]的数据,通过在Pipeline添加`SaveFileHandler`将数据保存成文件。 ### 构建 ### 如何使用 ### 相关文档 * [HttpPipeline说明](docs/HttpPipeline说明.md) * [Http协议说明](docs/Http协议说明.md) * [HttpHandler说明](docs/HttpHandler说明.md) * [UT说明](docs/UT说明.md) ### 示例 > 详情见module:http-helper-sample下说明文档README.MD > > 示例为SpringBootApplication,使用了`@EnableHttpHelper`开启功能. ##### Builder方式: > Builder方式提供灵活的配置方式创建HttpHelper对象,在SpringBoot项目中可以使用@Bean注解将对象放在容器中,以便注入使用. > > 示例功能:当前示例是能够根据传入的URL,将URL中全部的图片下载到本地. > > 创建HttpHelper对象代码片段如下: ~~~java /** * 请求并解析网页中图片 * @return */ @Bean public HttpHelper searchImgHttp(){ RequestTemplate template = RequestTemplateBuilder.builder() // 客户端配置 .buildClientConfig() .clientType(ClientType.WEB_CLIENT) .enableSsl(true) .enableCookie(true) .followRedirects(true) .threadNumber(4) .timeout(10_000) .privateClient(false) .requestConfig() // 添加一个HTML网页解析字段:类型为List,使用CSS解析, // 解析表达式为img@src ==> 表示获取网页中全部的图片连接 .addParseHtmlFields(new ParseField("imageList", ParseFieldType.LIST, "img@src", ExpressionType.CSS)) .method(HttpMethod.GET) .charset("UTF-8") // 添加:默认请求前处理器 .addHandler(DefaultHandlers.requestHandlers()) // 添加:解析响应内容类型 .addHandler(new ParseResponseTypeHandler()) // 添加:根据ParseHtmlFields解析HTML处理器 .addHandler(new ParseHtmlHandler()) .end() .build(); // 根据template创建一个HttpHelper对象 return HttpHelperBuilder.builder().builderByTemplate(template); } /** * 保存图片HttpHelper * @return * @throws URISyntaxException */ @Bean public HttpHelper saveImgHttp() throws URISyntaxException { RequestTemplate template = RequestTemplateBuilder.builder() // 客户端配置:使用默认OkHttpClient配置 .clientConfig(DefaultClientConfig.okHttpClientConfig(4,3_000,true)) .buildRequestConfig() .method(HttpMethod.GET) .charset("UTF-8") // 添加:默认请求前处理器 .addHandler(DefaultHandlers.requestHandlers()) // 添加:解析响应内容类型 .addHandler(new ParseResponseTypeHandler()) // 添加:保存文件Handler .addHandler(saveFileHandler()) .end() .build(); return HttpHelperBuilder.builder().builderByTemplate(template); } /** * 保存文件Handler * @return * @throws URISyntaxException */ @Bean public ResponseHandler saveFileHandler() throws URISyntaxException { Path path = Paths.get(this.getClass().getResource("/").toURI()); String savePath = path.toString()+"/download"; SaveFileHandler saveFileHandler = new SaveFileHandler(savePath); saveFileHandler.setFollowUrlPath(true); log.info("savePath:{}",savePath); return saveFileHandler; } ~~~ ##### Annotation方式: > Annotation可以十分方便的定义一个请求接口,通过动态Proxy生成具体实例,每一个方法会对应一个HttpHelper实例,根据@Get,@Post等注解生成对应的配置. > > 适用于:将一个http请求定义成声明式接口调用请求.更加适用于针对API定义对应的调用接口. ###### Annotation示例一:定义接口请求HTML和抽取HTML数据为实体 ~~~java /** * 定义一个接口,使用@HttpOperation注解 */ @HttpClient( clientType = ClientType.WEB_CLIENT, timeout = 10_000, enableSsl = true, enableCookie = true, followRedirects = true, privateClient = true ) @HttpOperation public interface TopBaidu { /** * 使用@Get(value=rootUrl),指定响应类型 * @return */ @Get( value = "http://top.baidu.com/buzz?b=1&fr=topindex", responseEntity = TopNews.class, charset = "GBK" ) TopNews getTopNews(); /** * 使用@Get,指定响应类型;通过传入url执行 * @return */ @Get( responseEntity = TopNews.class, charset = "GBK" ) TopNews getTopNewsByUrl(String url); } ~~~ ~~~java // 响应数据 @ResponseEntity public class TopNews { // 通过XPATH解析数据为一个List,List的Item是一个复杂对象 @ResponseField(fieldName = "newsItems",fieldType = ParseFieldType.LIST, parseExpression = "//table[contains(@class,'list-table')]/tbody/tr",expressionType = ExpressionType.XPATH) private List newsItems; } // List的Item @ResponseEntity public class NewsItem { // 使用相对的XPATH解析数据 @ResponseField(fieldType = ParseFieldType.STRING,parseExpression = "./td[contains(@class,'first')]",expressionType = ExpressionType.XPATH) private String number; @ResponseField(fieldType = ParseFieldType.STRING,parseExpression = ".//a[contains(@class,'list-title')]",expressionType = ExpressionType.XPATH) private String title; @ResponseField(fieldType = ParseFieldType.STRING,parseExpression = "./td[contains(@class,'tc')]/a[1]/@href",expressionType = ExpressionType.XPATH) private String newsLink; @ResponseField(fieldType = ParseFieldType.STRING,parseExpression = "./td[contains(@class,'tc')]/a[2]/@href",expressionType = ExpressionType.XPATH) private String videoLink; @ResponseField(fieldType = ParseFieldType.STRING,parseExpression = "./td[contains(@class,'tc')]/a[3]/@href",expressionType = ExpressionType.XPATH) private String imageLink; @ResponseField(fieldType = ParseFieldType.STRING,parseExpression = "./td[contains(@class,'last')]",expressionType = ExpressionType.XPATH) private String last; } ~~~ ###### Annotation示例二:根据API接口的说明文档定义API请求接口 >根据数据开放平台的接口描述,定义对应的接口,即可实现类似于@Feign的伪RPC声明式调用 > >接口文档:http://lbsyun.baidu.com/index.php?title=webapi/guide/webservice-placeapi ~~~java /** * 根据API接口的说明文档定义API请求接口 */ // 本接口全局Client @HttpClient( clientType = ClientType.OK_HTTP_CLIENT, privateClient = true, timeout = 3_000, threadNumber = 4, enableCookie = false,followRedirects = false,enableSsl = false ) // 本节口全局Request配置 @HttpRequest( rootUrl = "http://api.map.baidu.com/place/v2", contentType = ContentType.X_WWW_FORM_URLENCODED, responseType = ResponseType.JSON, method = HttpMethod.GET ) @HttpOperation public interface BaiduMapSearchApi { /** * 使用注解指定请求和响应实体,参数为实体 * @param path 请求路径 * @param param 参数实体类 * @return 响应的实体 */ @Get( requestEntity = SearchRegionParam.class,// 请求的参数实体类:属性有参数注解配置 responseEntity = SearchRegionResult.class,// 响应的实体类:可不指定,调用时传入 handlers = {BuildSnHandler.class}// 添加生成sn的url的请求处理器(内置默认处理器会自动全部添加) ) SearchRegionResult searchRegionByEntity(String path, SearchRegionParam param); /** * 使用注解配置请求,参数为Map,响应为泛型 * @param path 请求路径 * @param map Map参数 * @param outputClass 响应的类 * @param 响应的类型:可以为Map或JSON对应的实体类 * @return */ @Get( requestParams = {// 请求的参数注解配置 @RequestParam(fieldName = "query",required = true,example = "天安门、美食",description = "检索关键字"), @RequestParam(fieldName = "tag",example = "美食",description = "检索分类偏好"), @RequestParam(fieldName = "region",required = true,example = "北京、131(北京的code)、海淀区、全国,等",description = "检索行政区划区域"), @RequestParam(fieldName = "city_limit",defaultValue = "true",example = "true、false",description = "区域数据召回限制,为true时,仅召回region对应区域内数据。"), @RequestParam(fieldName = "extensions_adcode",defaultValue = "true",example = "true、false",description = "是否召回国标行政区划编码,"), @RequestParam(fieldName = "output",defaultValue = "json",example = "json或xml",description = "输出格式为json或者xml"), @RequestParam(fieldName = "scope",defaultValue = "1",example = "1、2",description = "检索结果详细程度。取值为1 或空,则返回基本信息;取值为2,返回检索POI详细信息"), @RequestParam(fieldName = "filter",example = "sort_name:distance|sort_rule:1",description = "检索过滤条件。当scope取值为2时,可以设置filter进行排序。"), @RequestParam(fieldName = "coord_type",defaultValue = "3",example = "1、2、3(默认)、4",description = "坐标类型,1(wgs84ll即GPS经纬度),2(gcj02ll即国测局经纬度坐标),3(bd09ll即百度经纬度坐标),4(bd09mc即百度米制坐标)"), @RequestParam(fieldName = "ret_coordtype",example = "gcj02ll",description = "可选参数,添加后POI返回国测局经纬度坐标"), @RequestParam(fieldName = "page_size",defaultValue = "10",example = "10",description = "单次召回POI数量,默认为10条记录,最大返回20条。多关键字检索时,返回的记录数为关键字个数*page_size。"), @RequestParam(fieldName = "page_num",defaultValue = "0",example = "0、1、2",description = "分页页码,默认为0,0代表第一页,1代表第二页,以此类推。"), @RequestParam(fieldName = "ak",required = true,description = "开发者的访问密钥,必填项。v2之前该属性为key。"), @RequestParam(fieldName = "sn",description = "开发者的权限签名。"), @RequestParam(fieldName = "timestamp",description = "设置sn后该值必填。") }, handlers = {BuildSnHandler.class}// 添加生成sn的url的请求处理器(内置默认处理器会自动全部添加) ) T searchRegionByMap(String path, Map map, Class outputClass); } ~~~ ##### Properties方式: ~~~yaml # 在application.yml中添加http-helper属性,即可使用@Autowired private HttpHelper httpHelper;注入对象. http-helper: client: client-type: web_client enable-cookie: true follow-redirects: true enable-ssl: true private-client: true thread-number: 4 timeout: 3000 request: charset: utf-8 content-type: empty default-headers: - name: Accept value: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8' method: get default-handler: true ~~~