Python 同、异步HTTP客户端封装:性能与简洁性的较量
一、前言
- 引入异步编程趋势:Python的异步编程正变得越来越流行。在过去,同步的HTTP请求已经不足以满足对性能的要求。
- 异步HTTP客户端库的流行:目前,有许多第三方库已经实现了异步HTTP客户端,如aiohttp和httpx等。然而,异步语法使得代码变得更加冗长,导致缩进增多,降低了代码的可读性和简洁性。
- 封装异步HTTP客户端:为了简化异步HTTP请求的代码,我们需要封装一个常用的HTTP客户端,以实现业务中常见的功能,并提供更简洁的接口。在这篇博客中,我将使用httpx库来进行封装异步客户端,requests则是封装同步客户端,以实现常见的HTTP方法,并支持设置超时时间、请求参数等功能。
原文:Python 同、异步HTTP客户端封装:性能与简洁性的较量
二、同异步http客户端测试
同异步简易Demo
再封装之前先看看同异步发个http请求的代码差异,这里以 requests、aiohttp、httpx进行展示
依赖安装
pip install requests aiohttp httpx
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# @Author: Hui
# @Desc: { 模块描述 }
# @Date: 2023/09/28 10:09
import asyncio
import httpx
import aiohttp
import requestsdef requests_demo(url):print("requests_demo")resp = requests.get(url)print(resp.text)async def aiohttp_demo(url):print("aiohttp_demo")async with aiohttp.client.ClientSession() as session:async with session.get(url) as resp:html_text = await resp.text()print(html_text)async def httpx_demo(url):print("httpx_demo")async with httpx.AsyncClient() as client:resp = await client.get(url)print(resp.text)async def main():url = "https://juejin.cn/"requests_demo(url)await aiohttp_demo(url)await httpx_demo(url)if __name__ == '__main__':asyncio.run(main())
可以看到同步的requests库实现的非常简洁一行代码就可以发送http请求。但异步语法的 httpx与aiohttp就感觉代码很臃肿,要嵌套好多层,尤其aiohttp,可读性变差了好多,但异步的请求可以大大的提升并发性能,利用网络IO的耗时处理更多的请求任务,这在爬虫中可以大大提升性能,再异步的web框架中也非常适用。
并发http请求测试
再看看同异步如何并发请求数据
async def concurrent_http_test():# requests testurls = ["https://juejin.cn/"] * 10start_time = time.time()for url in urls:requests_demo(url)use_time = time.time() - start_timeprint(f"requests {len(urls)} http req use {use_time} s")# httpx teststart_time = time.time()await asyncio.gather(*[httpx_demo(url) for url in urls])use_time = time.time() - start_timeprint(f"httpx {len(urls)} http req use {use_time} s")# aiohttp teststart_time = time.time()await asyncio.gather(*[aiohttp_demo(url) for url in urls])use_time = time.time() - start_timeprint(f"aiohttp {len(urls)} http req use {use_time} s")
结果:
requests 10 http req use 2.9108400344848633 shttpx 10 http req use 0.8657052516937256 saiohttp 10 http req use 1.9703822135925293 s
requests 请求demo是同步一个一个请求,所以会慢好多,而 httpx、aiohttp 是通过 asyncio.gather 并发请求的,会一次性发送10个请求,这样网络IO的耗时就复用了,但发现 aiohttp 的效果不尽人意,与httpx的0.86s相差太大,都是异步库,不应该的,于是看看之前写的demo代码发现其实aiohttp并没有复用 ClientSession 每次都是创建一个新的实例来去发送请求,这样频繁的创建与销毁连接会大大影响性能,httpx的 async with httpx.AsyncClient() as client:
好像是一样的问题,但httpx效果更好些。
尝试把 aiohttp 的 ClientSession 与 httpx.AsyncClient() 放到全局中去,再试试。
def requests_demo(url, session):# print("requests_demo")resp = session.get(url)return respasync def aiohttp_demo(url, aio_session):# print("aiohttp_demo")async with aio_session.get(url) as resp:# html_text = await resp.text()return respasync def httpx_demo(url, client):# print("httpx_demo")resp = await client.get(url)return respasync def concurrent_http_test():# requests testurls = ["https://juejin.cn/"] * 10start_time = time.time()with ThreadPoolExecutor() as pool:session = requests.session()for url in urls:pool.submit(requests_demo, url, session)use_time = time.time() - start_timeprint(f"requests {len(urls)} http req use {use_time} s")# aiohttp teststart_time = time.time()async with aiohttp.client.ClientSession() as aio_session:await asyncio.gather(*[aiohttp_demo(url, aio_session) for url in urls])use_time = time.time() - start_timeprint(f"aiohttp {len(urls)} http req use {use_time} s")# httpx teststart_time = time.time()async with httpx.AsyncClient() as client:await asyncio.gather(*[httpx_demo(url, client) for url in urls])use_time = time.time() - start_timeprint(f"httpx {len(urls)} http req use {use_time} s")
改进效果
requests 10 http req use 1.2176601886749268 saiohttp 10 http req use 0.4052879810333252 shttpx 10 http req use 0.5238490104675293 s
异步的效果很明显快了很多,requests 请求我也用 session 与线程池来并发请求看看效果,但网络有波动每次测的数据都不一样,所以这里的测试值仅作为参考。
三、异步http客户端封装
简易封装
aiohttp 与 httpx 性能都差不多,由于之前用 requests 习惯了,再接触这些异步封装的语法都觉得好怪,而 httpx的api 与 requests 类似,所以我就选择用 htppx 简单封装下。
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# @Author: Hui
# @Desc: { http客户端 }
# @Date: 2023/08/10 09:33
import httpx
from datetime import timedeltaclass HttpMethod(BaseEnum):GET = "GET"POST = "POST"PATCH = "PATCH"PUT = "PUT"DELETE = "DELETE"HEAD = "HEAD"OPTIONS = "OPTIONS"class RespFmt(BaseEnum):"""http响应格式"""JSON = "json"BYTES = "bytes"TEXT = "text"class AsyncHttpClient:"""异步HTTP客户端通过httpx封装,实现了常见的HTTP方法,支持设置超时时间、请求参数等,简化了异步调用的层级缩进。Attributes:default_timeout: 默认请求超时时间,单位秒default_headers: 默认请求头字典default_resp_fmt: 默认响应格式jsonclient: httpx 异步客户端response: 每次实例请求的响应"""def __init__(self, timeout=timedelta(seconds=10), headers: dict = None, resp_fmt: RespFmt = RespFmt.JSON):"""构造异步HTTP客户端"""self.default_timeout = timeoutself.default_headers = headers or {}self.default_resp_fmt = resp_fmtself.client = httpx.AsyncClient()self.response: httpx.Response = Noneasync def _request(self,method: HttpMethod, url: str,params: dict = None, data: dict = None,timeout: timedelta = None, **kwargs):"""内部请求实现方法创建客户端会话,构造并发送HTTP请求,返回响应对象Args:method: HttpMethod 请求方法, 'GET', 'POST' 等url: 请求URLparams: 请求查询字符串参数字典data: 请求体数据字典timeout: 超时时间,单位秒kwargs: 其他关键字参数Returns:httpx.Response: HTTP响应对象"""timeout = timeout or self.default_timeoutheaders = self.default_headers or {}self.response = await self.client.request(method=method.value,url=url,params=params,data=data,headers=headers,timeout=timeout.total_seconds(),**kwargs)return self.responsedef _parse_response(self, resp_fmt: RespFmt = None):"""解析响应Args:resp_fmt: 响应格式Returns:resp Union[dict, bytes, str]"""resp_fmt = resp_fmt or self.default_resp_fmtresp_content_mapping = {RespFmt.JSON: self.json,RespFmt.BYTES: self.bytes,RespFmt.TEXT: self.text,}resp_func = resp_content_mapping.get(resp_fmt)return resp_func()def json(self):return self.response.json()def bytes(self):return self.response.contentdef text(self):return self.response.textasync def get(self, url: str, params: dict = None, timeout: timedelta = None, resp_fmt: RespFmt = None, **kwargs):"""GET请求Args:url: 请求URLparams: 请求查询字符串参数字典timeout: 请求超时时间,单位秒resp_fmt: 响应格式,默认None 使用实例对象的 default_resp_fmtReturns:resp => dict or bytes"""await self._request(HttpMethod.GET, url, params=params, timeout=timeout, **kwargs)return self._parse_response(resp_fmt)async def post(self, url: str, data: dict = None, timeout: timedelta = None, resp_fmt: RespFmt = None, **kwargs):"""POST请求Args:url: 请求URLdata: 请求体数据字典timeout: 请求超时时间,单位秒resp_fmt: 响应格式,默认None 使用实例对象的 default_resp_fmtReturns:resp => dict or bytes"""await self._request(HttpMethod.POST, url, data=data, timeout=timeout, **kwargs)return self._parse_response(resp_fmt)async def put(self, url: str, data: dict = None, timeout: timedelta = None, resp_fmt: RespFmt = None, **kwargs):"""PUT请求Args:url: 请求URLdata: 请求体数据字典timeout: 请求超时时间,单位秒resp_fmt: 响应格式,默认None 使用实例对象的 default_resp_fmtReturns:resp => dict"""await self._request(HttpMethod.PUT, url, data=data, timeout=timeout, **kwargs)return self._parse_response(resp_fmt)async def delete(self, url: str, data: dict = None, timeout: timedelta = None, resp_fmt: RespFmt = None, **kwargs):"""DELETE请求Args:url: 请求URLdata: 请求体数据字典timeout: 请求超时时间,单位秒resp_fmt: 响应格式,默认None 使用实例对象的 default_resp_fmtReturns:resp => dict"""await self._request(HttpMethod.DELETE, url, data=data, timeout=timeout, **kwargs)return self._parse_response(resp_fmt)
封装细节
这里封装就是简单的内部维护一个 httpx 的异步客户端,然后初始化一些默认的参数
- default_timeout: 默认请求超时时间,单位秒,默认10s
- default_headers: 默认请求头字典
- default_resp_fmt: 默认响应格式json
- client: httpx 异步客户端
- response: 每次实例请求的响应
class AsyncHttpClient:"""异步HTTP客户端"""def __init__(self, timeout=timedelta(seconds=10), headers: dict = None, resp_fmt: RespFmt = RespFmt.JSON):"""构造异步HTTP客户端"""self.default_timeout = timeoutself.default_headers = headers or {}self.default_resp_fmt = resp_fmtself.client = httpx.AsyncClient()self.response: httpx.Response = None
然后实现几个常用的请求,get、post、put、delete方法
async def post(self, url: str, data: dict = None, timeout: timedelta = None, resp_fmt: RespFmt = None, **kwargs):"""POST请求Args:url: 请求URLdata: 请求体数据字典timeout: 请求超时时间,单位秒resp_fmt: 响应格式,默认None 使用实例对象的 default_resp_fmtReturns:resp => dict or bytes"""await self._request(HttpMethod.POST, url, data=data, timeout=timeout, **kwargs)return self._parse_response(resp_fmt)
每个请求方法冗余了一些常用的参数字段,例如
-
params 查询字符串入参
-
data body入参
-
timeout: 请求超时时间,单位秒
-
resp_fmt: 响应格式,默认None 使用实例对象的 default_resp_fmt
- 默认json,一般我们http的数据交互都是使用 json了
-
**kwargs 预留其他关键字参数的入参
- 这样有助于有些参数没想到要设计但经常用,可以通过kwargs来弥补
其实 get、post、put、delete方法没做什么事,就是标记了下使用什么请求方法、参数,最终都是让 _request
方法处理。
async def _request(self,method: HttpMethod, url: str,params: dict = None, data: dict = None,timeout: timedelta = None, **kwargs
):"""内部请求实现方法创建客户端会话,构造并发送HTTP请求,返回响应对象Args:method: HttpMethod 请求方法, 'GET', 'POST' 等url: 请求URLparams: 请求查询字符串参数字典data: 请求体数据字典timeout: 超时时间,单位秒kwargs: 其他关键字参数Returns:httpx.Response: HTTP响应对象"""timeout = timeout or self.default_timeoutheaders = self.default_headers or {}self.response = await self.client.request(method=method.value,url=url,params=params,data=data,headers=headers,timeout=timeout.total_seconds(),**kwargs)return self.response
处理完再根据指定的响应格式进行解析
def _parse_response(self, resp_fmt: RespFmt = None):"""解析响应Args:resp_fmt: 响应格式Returns:resp Union[dict, bytes, str]"""resp_fmt = resp_fmt or self.default_resp_fmtresp_content_mapping = {RespFmt.JSON: self.json,RespFmt.BYTES: self.bytes,RespFmt.TEXT: self.text,}resp_func = resp_content_mapping.get(resp_fmt)return resp_func()def json(self):return self.response.json()def bytes(self):return self.response.contentdef text(self):return self.response.text
通过字典的方法来处理不同的解析格式,简化了 if elif
的操作,这里封装主要是将一些常用操作封装起来,让代码更简洁,当然也可以获取响应对象后,自己自由处理,最后看看封装后的使用Demo
from py_tools.connections.http import AsyncHttpClient
from py_tools.enums.http import RespFmtasync def httpx_demo(url):print("httpx_demo")async with httpx.AsyncClient() as client:resp = await client.get(url)# print(resp.text)return respasync def main():url = "https://juejin.cn/"resp_obj = await httpx_demo(url)resp_text = resp_obj.textresp_text = await AsyncHttpClient().get(url, resp_fmt=RespFmt.TEXT)if __name__ == '__main__':asyncio.run(main())
封装后简洁了许多,虽然方法有些冗余参数,但在业务中使用就不会出现好多嵌套的缩进,也牺牲了一些灵活性,因为只封装一些常用的请求操作,但一开始也想不全,只有在业务中不断的磨练,以及大家一起提建议贡献,才能慢慢的变得更好用。有时候适当的冗余封装也挺不错的。
四、同步http客户端
同步的其实 requests 已经够简洁了,没必要再封装了,这里为了统一公共库的调用,就二次封装下,思路还是跟异步的一样,有一点不一样的就是,get、post、put、delete方法返回的是 self 的引用,用于一些链式操作。一开始我想把异步的也变成链式调用,发现做不到,方法如果不await拿不到结果,返回的是 协程对象,所以一时半会弄不出来,就用了一个参数的方式来处理。
class HttpClient:"""同步HTTP客户端通过request封装,实现了常见的HTTP方法,支持设置超时时间、请求参数等,链式调用Examples:>>> HttpClient().get("http://www.baidu.com").text>>> HttpClient().get("http://www.google.com", params={"name": "hui"}).bytes>>> HttpClient().post("http://www.google.com", data={"name": "hui"}).jsonAttributes:default_timeout: 默认请求超时时间,单位秒default_headers: 默认请求头字典client: request 客户端response: 每次实例请求的响应"""def __init__(self, timeout=timedelta(seconds=10), headers: dict = None):"""构造异步HTTP客户端"""self.default_timeout = timeoutself.default_headers = headers or {}self.client = requests.session()self.response: requests.Response = Nonedef _request(self,method: HttpMethod, url: str,params: dict = None, data: dict = None,timeout: timedelta = None, **kwargs):"""内部请求实现方法创建客户端会话,构造并发送HTTP请求,返回响应对象Args:method: HttpMethod 请求方法, 'GET', 'POST' 等url: 请求URLparams: 请求查询字符串参数字典data: 请求体数据字典timeout: 超时时间,单位秒kwargs: 其他关键字参数Returns:httpx.Response: HTTP响应对象"""timeout = timeout or self.default_timeoutheaders = self.default_headers or {}self.response = self.client.request(method=method.value,url=url,params=params,data=data,headers=headers,timeout=timeout.total_seconds(),**kwargs)return self.response@propertydef json(self):return self.response.json()@propertydef bytes(self):return self.response.content@propertydef text(self):return self.response.textdef get(self, url: str, params: dict = None, timeout: timedelta = None, **kwargs):"""GET请求Args:url: 请求URLparams: 请求查询字符串参数字典timeout: 请求超时时间,单位秒Returns:self 自身对象实例"""self._request(HttpMethod.GET, url, params=params, timeout=timeout, **kwargs)return selfdef post(self, url: str, data: dict = None, timeout: timedelta = None, **kwargs):"""POST请求Args:url: 请求URLdata: 请求体数据字典timeout: 请求超时时间,单位秒Returns:self 自身对象实例"""self._request(HttpMethod.POST, url, data=data, timeout=timeout, **kwargs)return selfasync def put(self, url: str, data: dict = None, timeout: timedelta = None, **kwargs):"""PUT请求Args:url: 请求URLdata: 请求体数据字典timeout: 请求超时时间,单位秒Returns:self 自身对象实例"""self._request(HttpMethod.PUT, url, data=data, timeout=timeout, **kwargs)return selfasync def delete(self, url: str, data: dict = None, timeout: timedelta = None, **kwargs):"""DELETE请求Args:url: 请求URLdata: 请求体数据字典timeout: 请求超时时间,单位秒Returns:self 自身对象实例"""self._request(HttpMethod.DELETE, url, data=data, timeout=timeout, **kwargs)return self
五、源代码
源代码已上传到了Github,里面也有具体的使用Demo,欢迎大家一起体验、贡献。
HuiDBK/py-tools: 打造 Python 开发常用的工具,让Coding变得更简单 (github.com)
相关文章:
Python 同、异步HTTP客户端封装:性能与简洁性的较量
一、前言 引入异步编程趋势:Python的异步编程正变得越来越流行。在过去,同步的HTTP请求已经不足以满足对性能的要求。异步HTTP客户端库的流行:目前,有许多第三方库已经实现了异步HTTP客户端,如aiohttp和httpx等。然而…...
无代码赋能数字化,云表搭桥铺路链接“数据孤岛”
什么是信息孤岛 企业数字化转型过程中,信息孤岛是一个突出的问题。这种情况发生的原因是,企业内部使用了多种应用软件,时间一长,员工在不同的系统中积累了大量的企业数据资产。然而,由于这些系统之间的数据无法互通&am…...
无需公网IP,实现公网SSH远程登录MacOS【内网穿透】
目录 前言 1. macOS打开远程登录 2. 局域网内测试ssh远程 3. 公网ssh远程连接macOS 3.1 macOS安装配置cpolar 3.2 获取ssh隧道公网地址 3.3 测试公网ssh远程连接macOS 4. 配置公网固定TCP地址 4.1 保留一个固定TCP端口地址 4.2 配置固定TCP端口地址 5. 使用固定TCP端…...
网络爬虫学习笔记 1 HTTP基本原理
HTTP原理 ~~~~~ HTTP(Hyper Text Transfer Protocol,超文本传输协议)是一种使用最为广泛的网络请求方式,常见于在浏览器输入一个地址。 1. URI和URL URL(Universal Resource Locator,统一资源定位器&…...
113. 路径总和ii
力扣题目链接(opens new window) 给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径。 说明: 叶子节点是指没有子节点的节点。 示例: 给定如下二叉树,以及目标和 sum 22, 在路径总和题目的基础上&…...
百度APP iOS端包体积50M优化实践(六)无用方法清理
一、前言 百度APP包体积经过一期优化,如无用资源清理,无用类下线,Xcode编译相关优化,体积已经有了明显的减少。但是优化后APP包体积在iPhone11上仍有350M的空间占用。与此同时百度APP作为百度的旗舰APP,业务迭代非常多…...
MySQL了解视图View (视图篇 一)
视图View是什么? MySQL的视图是一种虚拟表,它是基于一个或多个表的查询结果构建而成的。视图并不实际存储数据,而是根据定义的查询逻辑动态生成结果。 ----------------------------------- 视图的特点: - 虚拟表:…...
使用applescript自动化trilium的数学公式环境
众所周知,trilium什么都好,就是对数学公式的支持以及markdown格式的导入导出功能太拉了,而最拉的时刻当属把这两个功能结合起来的时候:导入markdown文件之后,原来的数学公式全没了,需要一个一个手动用ctrlm…...
idea中maven项目打包成jar,报错没有主清单属性解决方法
使用idea自带的打包可能会出现一下问题 在pom.xml中引入下面的依赖,即可解决 <build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><executions&…...
Caddy Web服务器深度解析与对比:Caddy vs. Nginx vs. Apache
🌷🍁 博主猫头虎 带您 Go to New World.✨🍁 🦄 博客首页——猫头虎的博客🎐 🐳《面试题大全专栏》 文章图文并茂🦕生动形象🦖简单易学!欢迎大家来踩踩~🌺 &a…...
基于PHP+MySQL的家教平台
摘要 设计和实现基于PHP的家教平台是一个复杂而令人兴奋的任务。这个项目旨在为学生、家长和教师提供一个便捷的在线学习和教授平台。本文摘要将概述这个项目的关键方面,包括用户管理、课程管理、支付处理、评价系统、通知系统和安全性。首先,我们将建立…...
吉利微型纯电,5 万元的快乐
熊猫骑士作为一款主打下层市场的迷你车型,吉利熊猫骑士剑指宝骏悦也,五菱宏光 MINI 等热门选手。 9 月 15 日,吉利熊猫骑士正式上市,售价为 5.39 万,限时优享价 4 .99 万元。价格和配置上对这个级别定位的战略车型有一…...
Gitee使用方法
Gitee是一个基于 Git 的代码托管和协作平台,具有免费、稳定等特点,并且能够与国内的Gitee社区、码云等服务相结合使用。 以下是使用Gitee的主要步骤: 注册账号:访问Gitee官网,点击“注册”按钮,填写注册信…...
前端适配笔记本缩放125%,150%导致页面错乱问题
由于前端在开发时使用的都是标准ui设计图,基本都是按照所以1920*1080, 而小屏幕笔记本由于分辨率高,所以导致的显示元素变小,因此很多笔记本的默认显示都是放大125%或者150%。 如果页面比较简单就让多余的空白单边扩展,…...
多线程的学习中篇下
volatile 关键字 volatile 能保证内存可见性 volatile 修饰的变量, 能够保证 “内存可见性” 示例代码: 运行结果: 当输入1(1是非O)的时候,但是t1这个线程并沿有结束循环, 同时可以看到,t2这个线程已经执行完了,而t1线程还在继续循环. 这个情况,就叫做内存可见性问题 ~~ 这…...
贪心算法-拼接字符串使得字典顺序最小问题
题目1 给定一个由字符串组成的数组strs,必须把所有字符串拼接起来,返回所有可能的拼接结果中,字典序最小的结果 思路:对数组排序,排序规则是对ab和ba的字符串进行比较大小,返回较小的顺序放到数组中最后将…...
Linux--互斥锁
一、与互斥锁相关api **互斥量(mutex)**从本质上来说是一把锁。在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量。对互斥量进行枷锁后,任何其他试图再次对互斥量加锁的线程将会被阻塞直到当前线程释放该互斥锁。如果释…...
[2023.09.21]:源码已上传,供大家了解Rust Yew的前后端开发
这个资源是Rust的源代码压缩包,供大家了解Rust Yew的前后端开发。 资源中的代码非常简洁易懂,虽然离商用场景还有一段距离,但是涵盖了前端的组件搭建、事件通信和反向代理,以及后端的Restful API的路由、功能实现和数据库访问。此…...
时序分解 | Matlab实现CEEMD互补集合经验模态分解时间序列信号分解
时序分解 | Matlab实现CEEMD互补集合经验模态分解时间序列信号分解 目录 时序分解 | Matlab实现CEEMD互补集合经验模态分解时间序列信号分解效果一览基本介绍程序设计参考资料 效果一览 基本介绍 Matlab实现CEEMD互补集合经验模态分解时间序列信号分解 1.分解效果图 ࿰…...
linux缓存-利用缓存提高性能的编程技巧
目录 利用缓存提高性能的编程技巧 实现方式 利用缓存提高性能的编程技巧 利用GCC编译器对齐属性 __attribute__((__aligned__(n))),利用处理器的缓存提高程序的执行速度; 使变量的起始地址对齐到一级缓存行长度的整数倍;使结构体对齐到一级缓存行长度…...
Socks5代理、IP代理与其在爬虫开发中的应用
在当今数字化时代,网络安全和数据获取变得愈发重要。代理服务器作为一种关键的技术手段,为网络工程师和爬虫开发人员提供了有力的工具。本文将深入探讨Socks5代理、IP代理以及它们在网络安全和爬虫应用中的角色与意义。 1. 代理服务器简介 代理服务器是…...
【C++】C++继承——切片、隐藏、默认成员函数、菱形
📝个人主页:Sherry的成长之路 🏠学习社区:Sherry的成长之路(个人社区) 📖专栏链接:C学习 🎯长路漫漫浩浩,万事皆有期待 上一篇博客:【C】STL…...
WebGL笔记:WebGL中绘制圆点,设定透明度,渲染动画
WebGL 绘制圆点 基于片元着色器来画圆形片元着色器在屏幕中画图是基于一个个的像素的每次画一个像素时,都会执行片元着色器中的main方法那么,我们就可以从这一堆片元中(n个像素点)找出属于圆形的部分片元的位置叫做 gl_PointCoord (一个点中片元的坐标位…...
华为云云耀云服务器L实例评测 | 实例使用教学之简单使用:通过命令行管理华为云云耀云服务器
华为云云耀云服务器L实例评测 | 实例使用教学之简单使用:通过命令行管理华为云云耀云服务器 介绍华为云云耀云服务器 华为云云耀云服务器 (目前已经全新升级为 华为云云耀云服务器L实例) 华为云云耀云服务器是什么华为云云耀云服务…...
微信小程序 课程签到系统
目录 前端页面展示主页面我的课程个人中心评论功能签到功能课程绑定超级管理员页面 前端文件结构文件结构app.json前端架构和开发工具前端项目地址 后端后端架构后端项目地址 注意事项 前端页面展示 主页面 登录页面: 账号是:用户名或者手机号 密码是&a…...
如何用Postman做接口自动化测试
前言 什么是自动化测试 把人对软件的测试行为转化为由机器执行测试行为的一种实践。 例如GUI自动化测试,模拟人去操作软件界面,把人从简单重复的劳动中解放出来。 本质是用代码去测试另一段代码,属于一种软件开发工作,已经开发完成…...
支付宝电脑网站支付,异步通知
一:异步通知是支付宝回调商户的服务器,所以这个地址需要通过外网访问,在真实项目中都会有对应的服务器,但是在测试中只有使用内网穿透工具 推荐使用NATAPP-内网穿透 基于ngrok的国内高速内网映射工具 配置好内网穿透之后不要忘记…...
【广州华锐互动】奶牛养殖难产助产3D沉浸式教学平台
在传统的奶牛难产助产教学中,主要依赖理论知识和2D图像来进行教学。然而,这种教学方式往往无法全面、真实地展示奶牛难产的各种情况,教学效果也不尽如人意。随着科技的发展,3D互动教学的出现,为奶牛难产助产教学带来了…...
IDEA社区版,真香!
IDEA(IntelliJ IDEA)是众多 Java 开发者的首选。 商业版的昂贵 IDEA 商业版(IntelliJ IDEA Ultimate)功能非常强大,能够满足 Java 开发的所有需求,但其高昂的价格…… 此时只能感叹,不是不想用…...
SpringBoot实现全局异常处理
1.全局异常处理介绍 1.1 简介 全局异常处理器即把错误异常统一处理的方法,可以在多个地方使用,而不需要为每个地方编写单独的处理逻辑。它可以帮助开发人员更好地管理异常,并提供一致的错误处理方式。 1.2 优点 1.全局异常处理可以提高代码…...
wordpress form manager 中文/网站排名顾问
怎么把pdf拆分成一页一个文件?我们经常会遇到PDF文档里面的页面比较多,但是有时候我们只需要其中的一部分内容,该怎么办呢,这个时候就需要把这个PDF文件拆分成多个pdf文件了,或者按页面,或者只需要其中一两…...
网站改版做301重定向/dw软件怎么制作网页
混杂模式 只要流经该机器的报文都会被抓取 抓包过滤器 简单使用 保存 停止抓包后保存。file->save 最好保存为pcap格式 选择解析方式 例如访问web网页默认是80端口,若访问了81端口,需要用decode as指定按什么协议解析 数据量跟踪 常用视图 常见协…...
网站优缺点/百度软文推广怎样收费
2019独角兽企业重金招聘Python工程师标准>>> chart.js 基于H5的canvas,轻量级的图表插件。 有6中图表类型:折线图、条形图、雷达图、饼图、柱状图、极地区域图 关于柱状图的绘制,追加 、更新、删除数据等操作的总结 原文来自于:h…...
网站建设原创文章 关键词/广州市网络seo外包
rpm 包安装过程中依赖问题“libc.so.6 is needed by XXX”解决方法(Liunx安装mysql报错) 原因:安装了64位的Linux 我安装的是 centOS 6.5 64位 64位的linux 对于 默认只安装主架构所需要的包 不会安装其他多余的包, 报错信息&…...
2015年做那些网站能致富/产品推广渠道
1.Jdk Linux(Centos)安装Java JDK及卸载 2.Tomcat (开机自启动) Linux(Centos)安装tomcat并且部署Java Web项目 3.CentOS7.3搭建ApachePHP7web SVNMariaDB Web服务器(2017-10-24) CentOS7.3搭建ApachePHP7web SVNMariaDB Web服务器(2017-10-2…...
潍坊住房与城乡建设局网站/发布平台
一、物理层概念 确定与传输媒体的接口有关的一些特性: (1)机械特性:描述接线器的形状。引脚数目和排列。线序的描述。 (2)电气特性:指明在接口电缆的个条线上出现的电压的范围。 (3…...