Javalin解构
Javalin
Javalin是一个轻量级http框架,我们可以很容易的了解请求的处理过程及其设计,具有较高的学习意义。
从demo说起
public static void main(String[] args) {Javalin app = Javalin.create(config -> {System.out.println("用户配置");});app.get("/", ctx -> {System.out.println("do" + ctx.path());ctx.result("Hello World");});// 处理异常app.exception(Exception.class, (e, ctx) -> {ctx.result("发生异常了");});// 处理异常状态app.error(404, ctx -> {ctx.html("Generic 404 message");});// 测试异常app.get("/exception/{e}", ctx -> {String msg = ctx.pathParam("e");if ("1".equals(msg)) {throw new InvalidAlgorithmParameterException("测试异常");} else if ("2".equals(msg)) {throw new ClassNotFoundException("Invalid");} else if ("3".equals(msg)) {ctx.status(404);} else {throw new Exception("Invalid algorithm parameter");}});//get请求{}参数解析app.get("/path/<path>", ctx -> ctx.result("Hello Path2 " + ctx.pathParam("path")));//post请求app.post("/json", ctx -> {Map<String, String> map = new HashMap<>();map.put("name", "张三");ctx.json(map);});//get请求<>参数解析app.get("/name/{name}", ctx -> {NaiveRateLimit.requestPerTimeUnit(ctx, 5, TimeUnit.MINUTES);ctx.result("Hello Path " + ctx.pathParam("name"));});// webscoketapp.ws("/websocket/{path}", ws -> {ws.onConnect(ctx -> System.out.println("Connected"));ws.onMessage(ctx -> {System.out.println("收到消息" + ctx.message());ctx.send("我收到了" + ctx.message());});});// 前置处理器app.before(ctx -> System.out.println("before" + ctx.path()));// 后置处理器app.after(ctx -> System.out.println("after" + ctx.path()));// 启动app.start(7070);}
创建Javalin实例
使用代码
Javalin app = Javalin.create(config -> {System.out.println("用户配置");});
实现原理
实现源码
public static Javalin create(Consumer<JavalinConfig> config) {// 初始化示例对象Javalin app = new Javalin();// 加载用户配置JavalinConfig.applyUserConfig(app, app.cfg, config); // mutates app.config and app (adds http-handlers)// 延迟检测服务是否正常启动JettyUtil.maybeLogIfServerNotStarted(app.jettyServer);return app;}
流程

get/post/websocket/before/after配置
使用方式
app.get("/", ctx -> {System.out.println("do" + ctx.path());ctx.result("Hello World");});
实现源码
public Javalin get(@NotNull String path, @NotNull Handler handler) {return addHandler(HandlerType.GET, path, handler);}public Javalin addHandler(@NotNull HandlerType httpMethod, @NotNull String path, @NotNull Handler handler) {return addHandler(httpMethod, path, handler, new RouteRole[0]); // no roles set for this route (open to everyone with default access manager)}public Javalin addHandler(@NotNull HandlerType handlerType, @NotNull String path, @NotNull Handler handler, @NotNull RouteRole... roles) {Set<RouteRole> roleSet = new HashSet<>(Arrays.asList(roles));javalinServlet.getMatcher().add(new HandlerEntry(handlerType, path, cfg.routing, roleSet, handler));eventManager.fireHandlerAddedEvent(new HandlerMetaInfo(handlerType, Util.prefixContextPath(cfg.routing.contextPath, path), handler, roleSet));return this;}
添加流程

get/post/websocket/before/after处理器都是这个类型,只是请求类型不一致,
http支持的请求类型有
GET, POST, PUT, PATCH, DELETE, HEAD, TRACE, CONNECT, OPTIONS, BEFORE, AFTER, INVALID;
websocket支持的请求类型有
WS_BEFORE, WEBSOCKET, WS_AFTER
exceptionHandler配置
使用方式
// 处理异常app.exception(Exception.class, (e, ctx) -> {ctx.result("发生异常了");});
实现源码
public <T extends Exception> Javalin exception(@NotNull Class<T> exceptionClass, @NotNull ExceptionHandler<? super T> exceptionHandler) {javalinServlet.getExceptionMapper().getHandlers().put(exceptionClass, (ExceptionHandler<Exception>) exceptionHandler);return this;}
添加流程

errorHandler配置
使用方式
// 处理异常状态app.error(404, ctx -> {ctx.html("Generic 404 message");});
实现源码
public Javalin error(int status, @NotNull String contentType, @NotNull Handler handler) {javalinServlet.getErrorMapper().addHandler(status, contentType, handler);return this;}
添加流程

权限管理配置
使用方式
// Set the access-manager that Javalin should use
config.accessManager((handler, ctx, routeRoles) -> {MyRole userRole = getUserRole(ctx);if (routeRoles.contains(userRole)) {handler.handle(ctx);} else {ctx.status(401).result("Unauthorized");}
});Role getUserRole(Context ctx) {// determine user role based on request.// typically done by inspecting headers, cookies, or user session
}enum Role implements RouteRole {ANYONE, ROLE_ONE, ROLE_TWO, ROLE_THREE;
}app.get("/un-secured", ctx -> ctx.result("Hello"), Role.ANYONE);
app.get("/secured", ctx -> ctx.result("Hello"), Role.ROLE_ONE);
实现源码
fun accessManager(accessManager: AccessManager) { pvt.accessManager = accessManager }
设置流程

启动流程
使用方式
// 启动
app.start(7070);
源码
public Javalin start(int port) {// 设置端口号jettyServer.setServerPort(port);// 启动服务return start();}
start()
public Javalin start() {// 记录启动开始时间long startupTimer = System.currentTimeMillis();// 如果已经启动了就报错if (jettyServer.started) {String message = "Server already started. If you are trying to call start() on an instance " +"of Javalin that was stopped using stop(), please create a new instance instead.";throw new IllegalStateException(message);}// 标识服务已经启动jettyServer.started = true;// 检测日志组件是否引入Util.printHelpfulMessageIfLoggerIsMissing();// 触发服务启动中事件eventManager.fireEvent(JavalinEvent.SERVER_STARTING);try {// 打印服务启动中JavalinLogger.startup("Starting Javalin ...");// 启动jetty服务jettyServer.start(javalinJettyServlet);// 打印javlin和java版本Util.logJavalinVersion();// 打印服务已经启动和耗时JavalinLogger.startup("Javalin started in " + (System.currentTimeMillis() - startupTimer) + "ms \\o/");// 发布服务已启动事件eventManager.fireEvent(JavalinEvent.SERVER_STARTED);} catch (Exception e) {// 打印服务启动异常JavalinLogger.error("Failed to start Javalin");// 发布服务启动失败事件eventManager.fireEvent(JavalinEvent.SERVER_START_FAILED);// 如果jetty已启动成功了,则停止服务if (Boolean.TRUE.equals(jettyServer.server().getAttribute("is-default-server"))) {stop();// stop if server is default server; otherwise, the caller is responsible to stop}if (e.getMessage() != null && e.getMessage().contains("Failed to bind to")) {// 端口冲突throw new JavalinBindException("Port already in use. Make sure no other process is using port " + Util.getPort(e) + " and try again.", e);} else if (e.getMessage() != null && e.getMessage().contains("Permission denied")) {// 权限不足throw new JavalinBindException("Port 1-1023 require elevated privileges (process must be started by admin).", e);}// 其他异常throw new JavalinException(e);}return this;}
其中核心逻辑在jettyServer.start(javalinJettyServlet);中
jettyServer.start(javalinJettyServlet);
fun start(wsAndHttpServlet: JavalinJettyServlet) {// 如果未设置端口则设置为8080端口if (serverPort == -1 && cfg.pvt.server == null) {serverPort = 8080JavalinLogger.startup("No port specified, starting on port $serverPort. Call start(port) to change ports.")}// 初始化默认sessionHandler管理sessioncfg.pvt.sessionHandler = cfg.pvt.sessionHandler ?: defaultSessionHandler()val nullParent = null // javalin handlers are orphans// 定义jetty请求处理器val wsAndHttpHandler = object : ServletContextHandler(nullParent, Util.normalizeContextPath(cfg.routing.contextPath), SESSIONS) {override fun doHandle(target: String, jettyRequest: Request, request: HttpServletRequest, response: HttpServletResponse) {request.setAttribute("jetty-target-and-request", Pair(target, jettyRequest)) // used in JettyResourceHandlernextHandle(target, jettyRequest, request, response)}}.apply {this.sessionHandler = cfg.pvt.sessionHandlercfg.pvt.servletContextHandlerConsumer?.accept(this)// 设置所有路径处理器addServlet(ServletHolder(wsAndHttpServlet), "/*")// 初始化websocket处理器JettyWebSocketServletContainerInitializer.configure(this) { _, _ ->/* we don't want to configure WebSocketMappings during ServletContext initialization phase */}}// 初始化jetty服务,并设置处理器server().apply {handler = if (handler == null) wsAndHttpHandler else handler.attachHandler(wsAndHttpHandler)if (connectors.isEmpty()) { // user has not added their own connectors, we add a single HTTP connectorconnectors = arrayOf(defaultConnector(this))}}.start()// 打印javalin logologJavalinBanner(cfg.showJavalinBanner)// 打印是否使用Loom(Java虚拟线程)LoomUtil.logIfLoom(server())// 初始化资源处理器(cfg.pvt.resourceHandler as? JettyResourceHandler)?.init() // we want to init this here to get logs in order// 打印服务使用的ip和端口server().connectors.filterIsInstance<ServerConnector>().forEach {JavalinLogger.startup("Listening on ${it.protocol}://${it.host ?: "localhost"}:${it.localPort}${cfg.routing.contextPath}")}// 非http服务打印绑定的端口server().connectors.filter { it !is ServerConnector }.forEach {JavalinLogger.startup("Binding to: $it")}// 设置服务启动的端口号serverPort = (server().connectors[0] as? ServerConnector)?.localPort ?: -1}
启动流程

请求处理流程
处理请求的入口
JavalinJettyServlet.service
这个类既处理http也处理websocket
override fun service(req: HttpServletRequest, res: HttpServletResponse) { // this handles both http and websocket// 判断是否有websocket请求标识if (req.getHeader(Header.SEC_WEBSOCKET_KEY) == null) { // this isn't a websocket request// http请求处理return httpServlet.service(req, res) // treat as normal HTTP request}// 去除项目路径前缀val requestUri = req.requestURI.removePrefix(req.contextPath)// 查找url对应后端处理器val entry = wsPathMatcher.findEndpointHandlerEntry(requestUri) ?: return res.sendError(404, "WebSocket handler not found")// 构建请求处理的上下文val upgradeContext = JavalinServletContext(req = req,res = res,cfg = cfg,matchedPath = entry.path,pathParamMap = entry.extractPathParams(requestUri),)// 校验权限if (!allowedByAccessManager(entry, upgradeContext)) return res.sendError(HttpStatus.UNAUTHORIZED.code, HttpStatus.UNAUTHORIZED.message)// 设置上下文req.setAttribute(upgradeContextKey, upgradeContext)// 设置协议头setWsProtocolHeader(req, res)// 处理websocket请求super.service(req, res) // everything is okay, perform websocket upgrade}
从源码可以知道http请求又调了JavalinServlet.service进行处理,websocket
掉jetty进行处理,会回调这个类型configure方法,因为这类继承了JettyWebSocketServlet类。
接下来先介绍http请求处理部分
JavalinServlet.service
override fun service(request: HttpServletRequest, response: HttpServletResponse) {try {// 构建请求处理的上下文val ctx = JavalinServletContext(req = request, res = response, cfg = cfg)// 定义请求处理的任务列表添加函数,添加到头部还是尾部val submitTask: (SubmitOrder, Task) -> Unit = { order, task ->when (order) {FIRST -> ctx.tasks.offerFirst(task)LAST -> ctx.tasks.add(task)}}// 去除项目的上下文路径val requestUri = ctx.path().removePrefix(ctx.contextPath())// 生成请求处理的任务列表(生成任务的最终顺序如下表)cfg.pvt.servletRequestLifecycle.forEach { it.createTasks(submitTask, this, ctx, requestUri) }// 处理请求ctx.handleSync()} catch (throwable: Throwable) {// 兜底异常处理exceptionMapper.handleUnexpectedThrowable(response, throwable)}}
| 处理器类型 | 是否跳过异常 |
|---|---|
| BEFORE | 是 |
| HTTP | 是 |
| ERROR | 否 |
| AFTER | 否 |
再看下处理请求部分
private fun JavalinServletContext.handleSync() {while (userFutureSupplier == null && tasks.isNotEmpty()) {// 取出第一个任务val task = tasks.poll()// 判断是否发生过异常且是否跳过异常if (exceptionOccurred && task.skipIfExceptionOccurred) {continue}// 处理任务handleTask(task.handler)}when {// 异步处理的请求userFutureSupplier != null -> handleUserFuture()// 非异步处理的请求,写入结果并打印日志else -> writeResponseAndLog()}}private fun <R> JavalinServletContext.handleTask(handler: TaskHandler<R>): R? =try {handler.handle()} catch (throwable: Throwable) {exceptionOccurred = trueuserFutureSupplier = nulltasks.offerFirst(Task(skipIfExceptionOccurred = false) { exceptionMapper.handle(this, throwable) })null}
然后在看下websocket的处理过程
首先调用configure完成连接初始化
JavalinJettyServlet.configure
// websocket初始化连接override fun configure(factory: JettyWebSocketServletFactory) { // this is called once, before everythingcfg.pvt.wsFactoryConfig?.accept(factory)factory.setCreator(JettyWebSocketCreator { req, _ -> // this is called when a websocket is created (after [service])val preUpgradeContext = req.httpServletRequest.getAttribute(upgradeContextKey) as JavalinServletContextreq.httpServletRequest.setAttribute(upgradeContextKey, preUpgradeContext.changeBaseRequest(req.httpServletRequest))val session = req.session as? Session?req.httpServletRequest.setAttribute(upgradeSessionAttrsKey, session?.attributeNames?.asSequence()?.associateWith { session.getAttribute(it) })// 初始化连接return@JettyWebSocketCreator WsConnection(wsPathMatcher, wsExceptionMapper, cfg.pvt.wsLogger)})}
重点看下WsConnection类
@OnWebSocketConnectfun onConnect(session: Session) {// websocket连接初始化val ctx = WsConnectContext(sessionId, session)tryBeforeAndEndpointHandlers(ctx) { it.wsConfig.wsConnectHandler?.handleConnect(ctx) }tryAfterHandlers(ctx) { it.wsConfig.wsConnectHandler?.handleConnect(ctx) }wsLogger?.wsConnectHandler?.handleConnect(ctx)}@OnWebSocketMessagefun onMessage(session: Session, message: String) {// 收到文本消息val ctx = WsMessageContext(sessionId, session, message)tryBeforeAndEndpointHandlers(ctx) { it.wsConfig.wsMessageHandler?.handleMessage(ctx) }tryAfterHandlers(ctx) { it.wsConfig.wsMessageHandler?.handleMessage(ctx) }wsLogger?.wsMessageHandler?.handleMessage(ctx)}@OnWebSocketMessagefun onMessage(session: Session, buffer: ByteArray, offset: Int, length: Int) {// 收到二进制消息val ctx = WsBinaryMessageContext(sessionId, session, buffer, offset, length)tryBeforeAndEndpointHandlers(ctx) { it.wsConfig.wsBinaryMessageHandler?.handleBinaryMessage(ctx) }tryAfterHandlers(ctx) { it.wsConfig.wsBinaryMessageHandler?.handleBinaryMessage(ctx) }wsLogger?.wsBinaryMessageHandler?.handleBinaryMessage(ctx)}@OnWebSocketClosefun onClose(session: Session, statusCode: Int, reason: String?) {// 连接关闭val ctx = WsCloseContext(sessionId, session, statusCode, reason)tryBeforeAndEndpointHandlers(ctx) { it.wsConfig.wsCloseHandler?.handleClose(ctx) }tryAfterHandlers(ctx) { it.wsConfig.wsCloseHandler?.handleClose(ctx) }wsLogger?.wsCloseHandler?.handleClose(ctx)ctx.disableAutomaticPings()}@OnWebSocketErrorfun onError(session: Session, throwable: Throwable?) {// 发生异常val ctx = WsErrorContext(sessionId, session, throwable)tryBeforeAndEndpointHandlers(ctx) { it.wsConfig.wsErrorHandler?.handleError(ctx) }tryAfterHandlers(ctx) { it.wsConfig.wsErrorHandler?.handleError(ctx) }wsLogger?.wsErrorHandler?.handleError(ctx)}
采用注解形式定义一系列websocket操作
请求处理流程

总结
至此,主要处理流程都介绍完毕,还有部分异步请求的处理下次再更新。最后附上Jetty核心组件结构图

相关文章:
Javalin解构
Javalin Javalin是一个轻量级http框架,我们可以很容易的了解请求的处理过程及其设计,具有较高的学习意义。 从demo说起 public static void main(String[] args) {Javalin app Javalin.create(config -> {System.out.println("用户配置"…...
yolov5算法,训练模型,模型检测
嘟嘟嘟嘟!工作需要,所以学习了下yolov5算法。是干什么的呢? 通俗来说,可以将它看做是一个小孩儿,通过成年人(开发人员)提供的大量图片的学习,让自己知道我看到的哪些场景需要提醒给成…...
linux系统防火墙开放端口
linux系统防火墙开放端口 在外部访问CentOS中部署应用时,需要通过防火墙管理软件,开端口,或者直接关闭防火墙进行解决(不建议) 加粗样式 常用命令: systemctl start firewalld #启动 systemctl stop firewalld #停止 systemctl status firewalld #查看…...
CSAPP第九章 虚拟内存
理解虚拟内存的原因 本章前部分描述虚拟内存是如何工作的,后一部分描述应用程序如何使用和管理虚拟内存 物理和虚拟寻址 虚拟内存作为缓存的工具 页表 页命中 缺页 虚拟内存作为内存管理的工具 简化链接,简化加载,简化共享,简化…...
numpy数组与矩阵运算(二)
文章目录矩阵生成与常用操作矩阵生成矩阵转置查看矩阵特性矩阵乘法计算相关系数矩阵计算方差、协方差、标准差计算特征值与特征向量计算逆矩阵求解线性方程组奇异值分解函数向量化矩阵生成与常用操作 矩阵生成 扩展库numpy中提供的matrix()函数可以用来把列表、元组、range对…...
Dubbo 中 Zookeeper 注册中心原理分析
Dubbo 中 Zookeeper 注册中心原理分析 文章目录Dubbo 中 Zookeeper 注册中心原理分析一、ZooKeeper注册中心1.1 ZooKeeper数据结构1.2 ZooKeeper的Watcher机制1.3 ZooKeeper会话机制1.4 使用ZooKeeper作为注册中心二、源码分析2.1 AbstractRegistry2.2 FailbackRegistry2.2.1 核…...
素数产生新的算法(由筛法减法改为增加法)--哥德巴赫猜想的第一次实际应用
素数产生新的算法(由筛法减法改为增加法)--哥德巴赫猜想的第一次实际应用 摘要:长期以来,人们认为哥德巴赫猜想没有什么实际应用的。 现在,我假设这个不是猜想,而是定理或公理,就产生了新的应用…...
递归-需要满足三个条件
一,概述 递归是一种应用非常广泛的算法(或者编程技巧)。很多数据结构和算法的编码实现都要用到递归,比如 DFS 深度优先搜索、前中后序二叉树遍历等。 去的过程叫“递”,回来的过程叫“归”。基本上所有的递归问题都可…...
【剑指Offer-Java】两个栈实现队列
题目 用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 ) 输入: [“CQueue”,“appendT…...
Allegro如何将Waived掉的DRC显示或隐藏操作指导
Allegro如何将Waived掉的DRC显示或隐藏操作指导 在用Allegro做PCB设计的时候,如果遇到正常的DRC,可以用Waive的命令将DRC不显示,如下图 当DRC被Waive掉的时候,如何将DRC再次显示出来。类似下图效果 具体操作如下 点击Display...
MATLAB——数据及其运算
MATLAB数值数据数值数据类型的分类1.整型整型数据是不带小数的数,有带符号整数和无符号整数之分。表中列出了各种整型数据的取值范围和对应的转换函数。2.浮点型浮点型数据有单精度(single)和双精度((double)之分&…...
【微信小程序】-- 页面导航 -- 声明式导航(二十二)
💌 所属专栏:【微信小程序开发教程】 😀 作 者:我是夜阑的狗🐶 🚀 个人简介:一个正在努力学技术的CV工程师,专注基础和实战分享 ,欢迎咨询! &…...
gdb查看汇编代码的例子
gdb查看汇编代码的例子 操作步骤 用 gdb 启动可执行文件:gdb executable_file在 gdb 中设置断点:break function_name 或者 break *memory_address运行程序:run当程序停止在断点处时,使用 disassemble 命令来查看汇编代码&#…...
第四讲:如何将本地代码与服务器代码保持实时同步
一、前言 在我们进行 Ambari 二次开发时,通常会先在服务器上部署一套可以使用的 Ambari 环境。 二次开发,就肯定是要改动代码的,我们不能老是在服务器上用vim编辑文件,那样效率太低,始终不是长久之计。 所以我们需要在本地打开我们的Ambari源码项目,比如用idea工具,可…...
cuda调试(一)vs2019-windows-Nsight system--nvtx使用,添加nvToolsExt.h文件
cuda调试 由于在编程过程中发现不同的网格块的结构,对最后的代码结果有影响,所以想记录一下解决办法。 CUDA的Context、Stream、Warp、SM、SP、Kernel、Block、Grid cuda context (上下文) context类似于CPU进程上下,表示由管理层 Drive …...
向Spring容器中注入bean有哪几种方式?
文章前言: 写这篇文章的时候,我正在手机上看腾讯课堂的公开课,有讲到 Spring IOC 创建bean有哪几种方式,视频中有提到过 set注入、构造器注入、注解方式注入等等;于是,就想到了写一篇《Spring注入bean有几种…...
如何用 Python采集 <豆某yin片>并作词云图分析 ?
嗨害大家好鸭!我是小熊猫~ 总有那么一句银幕台词能打动人心 总有那么一幕名导名作念念不忘 不知道大家有多久没有放松一下了呢? 本次就来给大家采集一下某瓣电影并做词云分析 康康哪一部才是大家心中的经典呢? 最近又有哪一部可能会成为…...
Python装饰器的具体实用示例
示例1:普通装饰器 def name(n):def func(x):res n(xx)return resreturn funcname def run(x): # run name(run)print(x)if __name__ __main__:run(1) # 2def name(n):def func(*x):res n(xx)return resreturn funcname def run(x): # run name(run)pr…...
谈谈我对Retrofit源码的理解
文章目录一、Retrofit简介二、使用介绍2.1 app / build.gradle添加依赖2.2 创建 Retrofit 实例2.3 创建 API 接口定义文件2.4 使用 Retrofit 进行网络请求三、源码分析3.1 创建 Retrofit 实例: 建造者模式创建Retrofit3.2 实例化API接口: 动态代理模式3.3 获取Observable返回值…...
八股文(三)
目录 一、 如何理解原型与原型链 二、 js继承 三、 vuex的使用 1.mutation和action的区别 mutation action 2.Vuex都有哪些API 四、 前端性能优化方法 五、 类型判断 题目 (1)typeof判断哪个类型会出错(即结果不准确)&…...
C++:std::is_convertible
C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...
Swift 协议扩展精进之路:解决 CoreData 托管实体子类的类型不匹配问题(下)
概述 在 Swift 开发语言中,各位秃头小码农们可以充分利用语法本身所带来的便利去劈荆斩棘。我们还可以恣意利用泛型、协议关联类型和协议扩展来进一步简化和优化我们复杂的代码需求。 不过,在涉及到多个子类派生于基类进行多态模拟的场景下,…...
cf2117E
原题链接:https://codeforces.com/contest/2117/problem/E 题目背景: 给定两个数组a,b,可以执行多次以下操作:选择 i (1 < i < n - 1),并设置 或,也可以在执行上述操作前执行一次删除任意 和 。求…...
select、poll、epoll 与 Reactor 模式
在高并发网络编程领域,高效处理大量连接和 I/O 事件是系统性能的关键。select、poll、epoll 作为 I/O 多路复用技术的代表,以及基于它们实现的 Reactor 模式,为开发者提供了强大的工具。本文将深入探讨这些技术的底层原理、优缺点。 一、I…...
css3笔记 (1) 自用
outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size:0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格ÿ…...
在WSL2的Ubuntu镜像中安装Docker
Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包: for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...
GruntJS-前端自动化任务运行器从入门到实战
Grunt 完全指南:从入门到实战 一、Grunt 是什么? Grunt是一个基于 Node.js 的前端自动化任务运行器,主要用于自动化执行项目开发中重复性高的任务,例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的“no matching...“系列算法协商失败问题
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的"no matching..."系列算法协商失败问题 摘要: 近期,在使用较新版本的OpenSSH客户端连接老旧SSH服务器时,会遇到 "no matching key exchange method found", "n…...
Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...
省略号和可变参数模板
本文主要介绍如何展开可变参数的参数包 1.C语言的va_list展开可变参数 #include <iostream> #include <cstdarg>void printNumbers(int count, ...) {// 声明va_list类型的变量va_list args;// 使用va_start将可变参数写入变量argsva_start(args, count);for (in…...
