首页 文章 精选 留言 我的

精选列表

搜索[文智公众趋势分析],共10000篇文章
优秀的个人博客,低调大师

【小白课程】一文带你初步了解 QML

QML是Qt框架构成中一种多用途语言,能够高效创建视觉效果更为丰富的用户界面。它与Qt的高性能图形功能结合使用,可以直接从QML应用程序中访问Qt的类型和其他功能。 其中,QML由对象声明、属性、绑定、信号和处理程序事件系统构成,如下所示: 上图中,声明了一个Window作为对象,包含Rectangle元素作为子对象,指定了Window对象的width、height、visible、title属性。Rectangle对象的宽度和高度与Window对象的宽度和高度绑定在一起,当改变Window对象宽度时,会发出widthChanged信号,onWidthChanged信号处理程序会在日志中记录新宽度。 1.什么是属性 属性是对象行为的基本方式,属性的数据类型决定了能够容纳的数据类型和可以对数据进行的操作。QML类型中定义了一组属性来作为对象的基本属性。创建对象类型的实例时都会带着为该对象类型定义的属性集。所有QML属性都与数据类型相关联,可以使用下列几种数据类型: 简单值类型,例如int、double、string和bool。 结构化值类型,例如rect和font。 对象类型不仅可以用于声明对象,还可以用作属性的类型。 特殊值类型,例如list、map和var。 2.什么是绑定 QML使用符合标准的JavaScript引擎,绑定可以包含JavaScript表达式或语句。绑定可以访问对象属性、调用方法,并且可以使用内置的Date和Math等内置的JavaScript对象,例如: height:parent.height/2表示该矩形的高度是其父级高度的一半。 height:Math.min(parent.width, parent.height)表示该矩形的高度取决于父级宽度或高度的最小值。 height:parent.height>100?parent.height:parent.height/2表示该矩形父级高度超过100,则高度等于父级高度,否则高度为父级高度的一半。 3.信号和处理机制 QML具有信号及其处理机制,应用程序和用户界面的组件之间需要信号来相互通信。信号是事件,来自对象的通知,代表发生某件事件,比如点击按钮等操作。发出信号时,需要相应的信号处理程序来响应,它是QML引擎调用的回调函数,在信号处理程序中会通过脚本或者其他操作逻辑来实现组件对事件的响应,举个例子: 在这个示例中,处理了MouseArea对象的clicked()信号。当点击该对象时,矩形位置会随机改变。 4.总结 QML语言支持声明式编程,允许声明应用程序的构建块,并使用各种属性对其进行修饰,而无需担心底层实现细节。同时QML也支持命令式编程来处理事件或操作数据,这些特点使得QML可读性强且易于上手,是设计师和开发人员都能使用的通用语言,方便将草图实现为应用程序。 此外,QML还可以扩展组件,支持封装组件来减低复杂性,并具备多平台和设备构建和部署应用程序的能力。

优秀的个人博客,低调大师

一文详解应用安全防护ESAPI

本文分享自华为云社区《应用安全防护ESAPI》,作者: Uncle_Tom。 1. ESAPI 简介 OWASP Enterprise Security API (ESAPI)是一个免费、开源的web应用程序安全控制库,使程序员更容易编写风险较低的应用程序。ESAPI库旨在使程序员更容易对现有应用程序进行安全性改造。ESAPI库也是新开发的坚实基础。 考虑到特定语言的差异,所有OWASP ESAPI版本都有相同的基本设计: 有一组安全控制接口。例如,定义了传递给安全控件类型的参数类型。 每个安全控制都有一个参考实现。例如:基于字符串的输入验证。例如,Java 的 org.owasp.ESAPI.reference.FileBasedAuthenticator 的 ESAPI,而其他参考实现则是成熟的企业级参考实现,例如,org.oasp.ESAPI.reference.DefaultEncoder 或 org.owasp.ESAPI.reference.DefaultValidator。 每个安全控件都有自己的实现(可选)。这些类中可能包含应用程序逻辑,这些逻辑可能由您的组织开发或为您的组织而开发。例如:企业身份验证。 为使本项目尽可能易于传播并使更多人能够自由自用,本项目的源代码使用了 BSD 许可证。本项目的文档使用了知识共享署名许可证。你可以随意使用、修改ESAPI,甚至将它包含在商业产品中。 2. ESAPI 框架 OWASP ESAPI 已经实现下面安全控件 身份认证 访问控制 输入验证 输出编码/转义 密码 错误处理和日志 通信安全 HTTP 安全 安全配置 ESAPI 框架 ESAPI 覆盖的OWASP Top 10 3. ESAPI 的使用 3.1. ESAPI 在 pom.xml 中的配置 目前最新的版本是: 2.5.3.1, 可以直接在Maven 库中找到。 <!-- https://mvnrepository.com/artifact/org.owasp.esapi/esapi --> <dependency> <groupId>org.owasp.esapi</groupId> <artifactId>esapi</artifactId> <version>2.5.3.1</version> <exclusions> <exclusion> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> </exclusion> </exclusions> </dependency> <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> 3.2. 注入类问题的防护 网络安全问题很大一部分是由于注入类问题造成的。这类问题主要是由于对外部输入使用的过程中转码不当造成的,例如:SQL 注入、命令注入、跨站脚本等等。 Encode(编码器接口)包含了许多解码输入和编码输出的方法,这样处理过的字符对于各种解释器都是安全的。 3.2.1. 防护:XSS 跨站脚本攻击 HTML编码器(encodeForHTML) @Test void testEncodeForHTML() { String input = "<a href='sdfs'></a> < script > alert('xss'); </ script >"; String encodedString = ESAPI.encoder().encodeForHTML(input); LOG.info("EncodeForHTML: {}", encodedString); } 输出: EncodeForHTML: &lt;a href&#x3d;&#x27;xss&#x27;&gt;&lt;&#x2f;a&gt; &lt; script &gt; alert&#x28;&#x27;xss&#x27;&#x29;&#x3b; &lt;&#x2f; script &gt; URL编码器(encodeForURL) @Test void testEncodeForURL() { String input = "/?callback=<script>alert('xss')</script>"; String encodedString; try { encodedString = ESAPI.encoder().encodeForURL(input); LOG.info("EncodeForURL: {}", encodedString); } catch (EncodingException e) { fail("Should not get exception:" + e.getMessage()); } } 输出: EncodeForURL: %2F%3Fcallback%3D%3Cscript%3Ealert%28%27xss%27%29%3C%2Fscript%3E 3.2.2. 防护:SQL 注入 @Test void testEncodeForSQL() { String userId = "tom' or '1=1'"; String sql = "select * from user where user='" + ESAPI.encoder().encodeForSQL(new MySQLCodec(MySQLCodec.Mode.STANDARD), userId) + "'"; LOG.info("sql = {}", sql); } 输出: sql = select * from user where user='tom\' or \'1\=1\'' 3.2.3. 防护:命令注入 @Test void testEncodeForOS() { String input = "dir & dir /s"; String cmd = ESAPI.encoder().encodeForOS(new WindowsCodec(), input); LOG.info("cmd = {}", cmd); } 输出: cmd = dir^ ^&^ dir^ ^/s 3.3. 输入校验问题的防护 网络安全最大的威胁是外部输入,所以对外部输入的校验对应用安全起着最大的防护作用。 3.3.1. ESAPI的输入校验 ESAPI有一个输入校验配置: validation.properties 给出了常用的校验。 validation.properties Validator.SafeString=^[.\\p{Alnum}\\p{Space}]{0,1024}$ Validator.Email=^[A-Za-z0-9._%'-]+@[A-Za-z0-9.-]+\\.[a-zA-Z]{2,4}$ Validator.IPAddress=^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$ Validator.URL=^(ht|f)tp(s?)\\:\\/\\/[0-9a-zA-Z]([-.\\w]*[0-9a-zA-Z])*(:(0-9)*)*(\\/?)([a-zA-Z0-9\\-\\.\\?\\,\\:\\'\\/\\\\\\+=&;%\\$#_]*)?$ Validator.CreditCard=^(\\d{4}[- ]?){3}\\d{4}$ Validator.SSN=^(?!000)([0-6]\\d{2}|7([0-6]\\d|7[012]))([ -]?)(?!00)\\d\\d\\3(?!0000)\\d{4}$ 校验接口 /** * 输入校验 * * @param context 校验内容 * @param input 校验输入 * @param type 校验类型,对应到 validation.properties 中的类型 * @param maxLength 输入字符最大长度校验 * @param allowNull 输入字符Null值校验,false - 不允许;true - 允许 * @return 校验失败返回 false,校验成功返回 true */ boolean ESAPI.validator().getValidInput(String context, String input, String type, int maxLength, boolean allowNull); @Test void testValidatorEmail() { String input = "xxxx.com"; if (!ESAPI.validator().isValidInput("", input, "Email", 11, false)) { LOG.error("Email validate fail!"); } else { LOG.info("Email is validate."); } } 输出: Email validate fail! 4. ESAPI 使用和升级过程中常遇到的问题 4.1. ExceptionInInitializerError 造成的 org.owasp.esapi.reference.DefaultEncoder CTOR threw exception org.owasp.esapi.errors.ConfigurationException: java.lang.reflect.InvocationTargetException Encoder class (org.owasp.esapi.reference.DefaultEncoder) CTOR threw exception. at org.owasp.esapi.util.ObjFactory.make(ObjFactory.java:129) at org.owasp.esapi.ESAPI.encoder(ESAPI.java:101) at com.test.esapi.EsapiTest.testUpdateJulietInfo_good(EsapiTest.java:19) at java.base/java.util.ArrayList.forEach(ArrayList.java:1511) at java.base/java.util.ArrayList.forEach(ArrayList.java:1511) Caused by: java.lang.reflect.InvocationTargetException at org.owasp.esapi.util.ObjFactory.make(ObjFactory.java:86) ... 71 more Caused by: java.lang.ExceptionInInitializerError at java.base/java.lang.Class.forName0(Native Method) at java.base/java.lang.Class.forName(Class.java:375) at org.owasp.esapi.util.ObjFactory.loadClassByStringName(ObjFactory.java:158) at org.owasp.esapi.util.ObjFactory.make(ObjFactory.java:81) at org.owasp.esapi.ESAPI.logFactory(ESAPI.java:139) at org.owasp.esapi.ESAPI.getLogger(ESAPI.java:155) at org.owasp.esapi.reference.DefaultEncoder.(DefaultEncoder.java:85) at org.owasp.esapi.reference.DefaultEncoder.(DefaultEncoder.java:109) at org.owasp.esapi.reference.DefaultEncoder.getInstance(DefaultEncoder.java:68) ... 76 more Caused by: org.owasp.esapi.errors.ConfigurationException: Unable to locate resource: esapi-java-logging.properties at org.owasp.esapi.logging.java.JavaLogFactory.readLoggerConfiguration(JavaLogFactory.java:128) at org.owasp.esapi.logging.java.JavaLogFactory.(JavaLogFactory.java:96) ... 85 more 这个是 ESAPI 升级到 2.5.0.0 之后的版本时,最常碰到的问题。 2.5.0.0 是 ESAPI 一个重要的变动版本,我们从版本变更信息中可以看到: 此版本 ESAPI 全面放弃了被 Log4J 不断的漏洞困扰的 Log4J 的支持,转而使用 SLF4J。如果您的 ESAPI.Logger 属性设置为使用 Log4J,如果不更改它,将引发模糊的 Exceptions 或 Errors,通常为 ExceptionInInitializerError。 对 AntiSamy 升级到了 1.7.0,并支持了 AntiSamy 自定义的 AntiSamy-sapi.xml 文件。 从上一个版本开始,此版本仅支持 Java 8 或更高版本。 从这里可以看到 ExceptionInInitializerError 应该是变更日志组件造成的,熟悉的朋友会立刻想到 ESAPI 的配置文件 ESAPI.properties 里面给出所有组件的配置。 从图中 69 到 72 行可以看到,69 行的: ESAPI.Logger=org.owasp.esapi.logging.java.JavaLogFactory 还是指向 org.owasp.esapi.logging.java.JavaLogFactory, 后面的注释也提醒我们需要修改成 71 行的: ESAPI.Logger=org.owasp.esapi.logging.slf4j.Slf4JLogFactory 修改后问题解决。 4.2. java.lang.NoClassDefFoundError: javax/servlet/http/HttpServletRequest java.lang.NoClassDefFoundError: javax/servlet/http/HttpServletRequest at java.base/java.lang.Class.getDeclaredMethods0(Native Method) at java.base/java.lang.Class.privateGetDeclaredMethods(Class.java:3402) at java.base/java.lang.Class.getMethodsRecursive(Class.java:3543) at java.base/java.lang.Class.getMethod0(Class.java:3529) at java.base/java.lang.Class.getMethod(Class.java:2225) at org.owasp.esapi.util.ObjFactory.loadMethodByStringName(ObjFactory.java:196) at org.owasp.esapi.util.ObjFactory.findSingletonCreateMethod(ObjFactory.java:173) at org.owasp.esapi.util.ObjFactory.make(ObjFactory.java:84) at org.owasp.esapi.ESAPI.validator(ESAPI.java:192) at com.huawei.hwe.esapi.EsapiTest.testEsapi_encodeForURL(EsapiTest.java:23) at java.base/java.util.ArrayList.forEach(ArrayList.java:1511) at java.base/java.util.ArrayList.forEach(ArrayList.java:1511) Caused by: java.lang.ClassNotFoundException: javax.servlet.http.HttpServletRequest at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641) at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:525) ... 79 more 这个问题明显是缺少了依赖库。我们可以查看 Maven 库关于 ESAPI 的定义和依赖信息。 https://mvnrepository.com/artifact/org.owasp.esapi/esapi/2.5.3.1 编译依赖 运行依赖 从运行依赖可以看到需要:javax.servlet » javax.servlet-api 点开后面的版本号:3.1.0,就可以得到 javax.servlet-api 3.1.0 的 mvn 依赖配置,将这个配置加到工程的 pom.xml 文件中就可以了。 在 pom.xml 中加入 javax.servlet-api 配置后,问题解决。 <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> 点击关注,第一时间了解华为云新鲜技术~

优秀的个人博客,低调大师

一文聊聊代码的可读性

观点表达 上图代码中,你觉得左边实现更好,还是右边的实现更好 ?你的代码是更像左边还是更接近右边?首先说下,这个例子不是很好,但是我觉得又挺好,因为这个例子不够极端,但是针对这样的case去阐述观点,可以更加明确;我的观点,也是本文接下去的观点,是倾向于右边,当然在这个例子中,可能大家会觉得右边的代码有点没有必要的复杂化了,左边的代码非常简洁明晰,这里说下观点背后的思考: a.右边的代码更好扩展,职责边界清晰,富有层次; b.右边代码读起来更通俗,更贴近人的语言; c.右边的代码可以更快的让你知道方法的意图(这里要说下,因为例子简单,可能大家觉得左边代码也很快能get到意图,这里说下1. 如果方法实现稍微复杂一点,左边的理解难度就要高上一点;2. 从扫眼看去,还是右边的意图更直接,虽然这里例子可能差不多了几秒)虽然可能刚看个头,就会有一些意见上的分歧,但是我觉得本身这个命题还属于开放式的,如果是个封闭式的题目,相关的书应该已经去明确定义清楚如何去coding了,希望大家可以继续辩证地往下看,疑义相与析; 可读性理解 说可读性理解前,我们先来说说重构,我们经常做重构这件事情,小则一个月,长则一年,那我们做重构主要是为了这么几个目标:1. 让代码跑得更快;2. 让代码清爽点,精简点;3. 抽象一些方法,使用一些模式,让代码有更好的复用性和扩展性;4. 让代码读起来更流畅点;说到这里,又想问大家个问题,就上面几个目标,你觉得哪个目标的优先级最高 ? 就我个人来说,以前做重构,为了性能,场景不多,也没有为了精简去重构,精简更多的是重构过程中的一个附带的赠品,更多的是模型的调整,共同方法的抽象、沉淀,调用层次的梳理,比较耳熟能详的分层结构:DAO层、model层、manager层、biz层和service层,再说纯粹为了代码更好的去重构,基本上没有; 就此时此刻,站在一个业务技术团队的开发视角看,可读性是代码的第一优先级要求; Readability is the code’s aptitude for communicating its intent. This means that if we assume the code works as intended, it is very easy to figure out what the code does. [可读性是指能很好的让维护者看出代码的意图,并且实际的代码执行逻辑确实是按照维护者所直观感受到的意图来执行的] 以前在编译器还不是那么完善和智能的时候,我们还会去考虑什么样的代码,什么样的语法和调用,可以最大化的提升代码的执行速度,当时真的还蛮乐此不疲的去研究,去get这样的一些小小的提升,感觉自己很“专业”,但是这样的专业,随着时间的发展,基本上compiler目前可以比我们做的更好了,所以我们在如何和机器相处的更好的话题上,以及被compiler完全接管了,那这个时候,我们需要做的更多的是去如何和代码后面的reader(可能是你也可能是别人)做好沟通; 我们现在去迭代开发需求,花的时间的大头不在写代码上,还是在理解代码以及这段代码的上下游代码逻辑上,可能在理解的过程中还会发出一些谩骂声,然后还会下意识的去看看author ~ 在我们说的很多的一个词,可维护性Maintainability,可以体现在,在做一个变更的时候,我们需要去阅读多少上下游或者相关的代码,涉及面越广,出错或者遗漏某些潜在变动的概率就大,可维护性就低;从另一个视角看,如果我们在做一个变更的时候,代码可读性越差,花的时间越长,这部分功能或者模块的可维护性就越低; 因此可读性对于维护和迭代来说,都是一个关乎效率和稳定性的关键因素; 如何提升可读性 在show观点的时候,已经提到了可读性体现优势的三个方面:更贴近人的语言(通俗表达)、方法的意图(明确意图)、层次(层次结构); 1. 通俗表达 我认为最通俗的表达,其实就是以前我们会有个习惯,在写代码之前会写伪代码,伪代码就是一种非常对co-worker友好的代码形式的表达,来看看下面这段例子(从现有工程代码里面找的)。 从这个例子看到,最通俗的表达是哪个?应该是1-9这几个comments,次之是各个备注下面的方法的命名(因为可能中翻英不是那么地道); 我们代码中对自己人最友好的就是备注,(但是有时候也会有意外,如果这个备注是过期的话),备注下方对应的方法名,可能不一定对人那么友好,一个原因是我们对于中文翻译到英文,如何翻译得准确和地道,其实拿捏不好,因为有时候我们翻译能力受限的时候是拼音+英文混搭的;另外一个原因是我们在定义方法名称的时候,没有描述到位这个方法所做的事情,要么就是描述一半,要么描述很抽象,似乎get到了点什么,似乎好像看了又等于没看; 通俗的表达的前提是有层次、有抽象、有封装,这块具体下面来讲,可以体感下这个例子: 这段代码算通俗吗?也许可能每一行都能读懂什么意思,但是整体在一起,这个if到底判断了啥,是不知道的(当然,我很取巧的把注释给你们拿了哈,这里其实这个注释的英文命名就是这个if语句条件抽象成的方法的最好定义) 这里对于通俗的表达的定义涵盖两个方面:1. 足够友好的能让后面读你代码的人能够读懂,并且是直观的通顺的理解;2. 足够准确的去描述这个方法所做的事情;再者,像遵循约定、变量/方法/类的合理命名、定义完整的文件名、规范的格式和空格空行的运用、备注等,都能帮助后续别人在读你的代码的时候,可以轻松并且明确的驾驭你的意图的; 2. 明确意图 明确的意图本质上是现实和预期的匹配:1. 现实:代码作者在写这个方法的具体实现;2.预期:读代码的人在看到这个方法的时候心里预期的实现;两者如何是匹配的,那我们定义为这段代码是有明确意图,并且没有side effect的; side effect就是副作用,也可以理解为是否在明确定义的方法之下有夹带行为,这类事情其实代码中犯的很多,而且属于大家觉得不应该犯,但是多多少少个人在写的时候,都是潜意识的没注意到,看下这个例子,非常典型: 因为我们用cache、tair用的就比较多,对于tair的常规更新策略都是读未命中之后溯源查询之后回写,然后名称可以保证90%以上的都是getXXXFromCache,但是里面做着更新的事情,但是这个事情又说回来,如果整体团队都形成共识,这样的共识如果变成一种约定,可能倒也不算在side effect的范畴,但是这里我只是举了个例子; 上面说的这类是属于方法的名称没有明确的去表达方法内部的实现的范畴,还有一类,其实属于Functional Programming的范畴(FP本身是另外一个领域,这里不细说),在FP中有一个严格的要求和规范,就是不能变更入参,要保证多次执行之后的结果是一致的(框架实现中的基于context传递的面向过程的编程方式不在此列),看下下面这个例子: 代码比较长,我把其他代码删除了,这个对于入参的修改还是比较隐形的,但是本质上是违背了FP的原则,同时后续迭代的人是不知道这个逻辑的,非常容易踩坑,因为在这里被变更掉的request的成员,可能会在之后的流程中成为重要的处理对象,但是如果后面迭代同学的预期是这个成员变量要和接口入参一致的时候,他会一脸懵逼的发现情况有点超乎自己的想象,这时候要么arthas,要么就是debug了; side effect最大的问题大于惯性犯错,因为我们都是秉持相信上一代作者的设计、意图和结构,去做后面的需求迭代的,这时候意图的错误传达,会导致异常乃至故障的发生,同时这样的问题还非常不好排查,因为这个漏洞属于存在于惯性思维的掩护之下的; 3. 层次结构 这里说的层次结构是复杂方法内的代码层次结构,不是以前我们说的比较多的应用分层的层次结构,从一个例子入手: 这个方法freeTieClip内部实现,调用了两个封装的方法,分别是make_item和add_item,在这个方法之前,是一段购物车商品的遍历,array_index和for loop其实和后面两个的方法调用,应该不属于一个层次的抽象,因为前两者是直接的pass through的调用(可以理解为通过 . 来执行调用的),后者属于function call,因此正确的层次描述如下: 那针对这个freeTieClip方法的层次结构优化手段也很简单,就是给前面两个的pass through调用封装一个方法: 接着,如果我们要增加一个从购物车中按照name来删除一个商品的方法remove_item_by_name,大致的逻辑如下: 那这个方法应该是处于哪一个层次结构呢?可能的结果有如下几种: 从方法的命名的业务语义上来说,上面两层new layer above和top layer都不太合适,那从直观来说,应该是bottom layer比较符合,因为再往下down的一层new layer below是属于base的实现了;最终的层次如下所示: 例子中所描述的分层的方法,具体在实践中可以参考,倒是不用这么严格地去做,但是这个思路还是非常有借鉴意义的,至少是赋予了一个模糊的、不好定义的可读性设计和实现提供了一种方法论; 执行过程中的建议 上面给了一些定义说明和一种分层方式,但是部分靠理解,另外一部分靠实践慢慢来体会和熟练,都属于不是马上可以应用上的,那面向如何快速在实践中做一些针对可读性的提升和做可读性待提升代码的识别,下面给几条tips,完全是for实操,背后不一定有理论能对应背书的; 1.Five Lines a.直接解释,方法实现不要超过5行,非常极端的要求,但是不是去follow这个规范,拿这个标准可以去识别潜在的可被优化的代码,帮助你去嗅到bad smell; 2.Either call or pass a.这个和上面说的分层比较像,但是没有分层那么逻辑严谨,只是一个底线可识别的逻辑,在java中可以以. 符号调用为基础操作,call具体方法,视为高级操作,下图有示意,尽量避免两种调用方式在同一个方法内; 3.Use pure conditions a.pure conditions是指该条件的运行没有额外的影响,这个和fp的要求一致,多次运行这个条件判断,1.结果是幂等的,2.没有其他隐含的行为动作,纯判断;no side effect; 4.if only at the start a.方法里面如果有if语句,尽量在方法的开头,如果在方法体中间有,那么需要把if整块语句拆分出去一个方法; 5.Stay Away From Comments a.远离注释(非方法声明注释),是个理想主义要求,但是这个tips得出的逻辑是:1.好的代码可以非常直接的表明自身的作用;2.注释是有时效性的,会随着代码的迭代而失去维护,过期,不正确;3.一个有误导的,不正确的注释会带来比较大的后续影响; b.同样的,可以重新审视有注解的代码块,看看是否有优化的空间; 代码生成的结合 面向可读性的重构,其实是对代码结构的重新梳理,让方法更内聚,让层次更合理,目前业界用的比较多的代码生成工具是github copilot,底层基于GPT3,代码生成工具的强项其实还是在所谓的基础方法生成的范畴,业务方法的生成,应该还是离直接使用比较难,因为业务代码的生成,需要理解业务,理解商业,这个考虑到目前各类copilot训练的数据集来说,基本上不太可能完成; 从上面的层次结构的划分来说,其实copilot针对上面的“购物车基础操作层”、“商品基础操作层”中的方法,应该都可以比较好的去生成对应的代码,并且能具备比较好的代码接受率,同时这类方法都是比较容易通过单测来保障实现的正确性的; 基于以上,个人觉得后面业务团队的编码方式会产生这样的变化:业务技术同学实现业务语义到通俗编程语言语义的转换,主要包括方法的定义(包含方法的职责,实现的功能定义),方法的调用顺序(主要面向业务规则和需求),方法调用失败或异常的处理等,然后具体的子方法的内部实现,可以通过各类的代码生成工具进行,并且可以通过工具生成对应的单测,也许后面更进一步,业务产品定义代码执行流程,技术同学都不需要做programming,只需要去做验证生成的代码是否正确的debug工作。 结语 作为程序员,我们对于自己写的代码有这么几层要求:1、我们的代码是可以正常运行并且能满足业务要求的;2、你写的代码结构是要符合当前应用或者当前架构的规范和约束的,比如包规范、命名规范、格式规范,嵌套不能多于3层,单方法不能超过10行等约束;3、你写的代码是要具备比较高的可读性的,其他程序员可以非常直白的读懂你的代码(这里绝不是指一行行读完,然后去猜想你的代码所表达的业务逻辑,而是非常直观的能get到你的代码逻辑和设计意图); 高可读性的代码编写虽说是一种技能,但是要落更多靠的还是氛围和土壤,因为本质上大家应该都能写出可读性高的代码,有时候是因为没有主动认知到,有时候是惯性的follow原系统中的“风格”,这里个人比较建议的做法是在第一遍实现代码完成自测满足业务诉求之后,回头立马针对可读性重构下自己的代码,这时候成本比较小,效果也非常不错,如果重构范围能稍微延伸到一定范围内的上下游关联代码,那更好,供借鉴;欢迎大家基于上面的表述给予建议。 作者 | 江丹阳 原文链接 本文为阿里云原创内容,未经允许不得转载。

优秀的个人博客,低调大师

文盘Rust -- FFI 浅尝 | 京东云技术团队

rust FFI 是rust与其他语言互调的桥梁,通过FFI rust 可以有效继承 C 语言的历史资产。本期通过几个例子来聊聊rust与C 语言交互的具体步骤。 场景一 调用C代码 创建工程 cargo new --bin ffi_sample Cargo.toml 配置 [package] name = "ffi_sample" version = "0.1.0" edition = "2021" build = "build.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [build-dependencies] cc = "1.0.79" [dependencies] libc = "0.2.146" libloading = "0.8.0" 编写一个简单的c程序sample.c int add(int a,int b){ return a+b; } main.rs use std::os::raw::c_int; #[link(name = "sample")] extern "C" { fn add(a: c_int, b: c_int) -> c_int; } fn main() { let r = unsafe { add(2, 18) }; println!("{:?}", r); } build.rs fn main() { cc::Build::new().file("sample.c").compile("sample"); } 代码目录树 . ├── Cargo.lock ├── Cargo.toml ├── build.rs ├── sample.c └── src └── main.rs cargo run 场景二 使用bindgen 通过头文件绑定c语言动态链接库 修改Cargo.toml,新增bindgen依赖 [package] name = "ffi_sample" version = "0.1.0" edition = "2021" build = "build.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [build-dependencies] cc = "1.0.79" bindgen = "0.65.1" [dependencies] libc = "0.2.146" libloading = "0.8.0" 新增 sample.h 头文件 #ifndef ADD_H #define ADD_H int add(int a, int b); #endif 新增 wrapper.h 头文件 wrapper.h 文件将包括所有各种头文件,这些头文件包含我们想要绑定的结构和函数的声明 #include "sample.h"; 改写build.rs 编译 sample.c 生成动态链接库sample.so;通过bindgen生成rust binding c 的代码并输出到 bindings 目录 use std::path::PathBuf; fn main() { // 参考cc 文档 println!("cargo:rerun-if-changed=sample.c"); cc::Build::new() .file("sample.c") .shared_flag(true) .compile("sample.so"); // 参考 https://doc.rust-lang.org/cargo/reference/build-scripts.html println!("cargo:rustc-link-lib=sample.so"); println!("cargo:rerun-if-changed=sample.h"); let bindings = bindgen::Builder::default() .header("wrapper.h") .parse_callbacks(Box::new(bindgen::CargoCallbacks)) .generate() .expect("Unable to generate bindings"); let out_path = PathBuf::from("bindings"); bindings .write_to_file(out_path.join("sample_bindings.rs")) .expect("Couldn't write bindings!"); } 修改main.rs include 宏引入sample 动态链接库的binding。以前我们自己手写的C函数绑定就不需要了,看看bindings/sample_bindings.rs 的内容与我们手写的函数绑定是等效的 include!("../bindings/sample_bindings.rs"); // #[link(name = "sample")] // extern "C" { // fn add(a: c_int, b: c_int) -> c_int; // } fn main() { let r = unsafe { add(2, 18) }; println!("{:?}", r); } 代码目录树 . ├── Cargo.lock ├── Cargo.toml ├── bindings │ └── sample_bindings.rs ├── build.rs ├── sample.c ├── sample.h ├── src │ └── main.rs └── wrapper.h ffi_sample 工程的完整代码位置,读者可以clone https://github.com/jiashiwen/wenpanrust,直接运行即可 cargo run -p ffi_sample 场景三 封装一个c编写的库 secp256k1是一个椭圆曲线计算的 clib,这玩意儿在密码学和隐私计算方面的常用算法,下面我们从工程方面看看封装secp256k1如何操作 cargo new --lib wrapper_secp256k1 cargo.toml [package] name = "wrapper_secp256k1" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [build-dependencies] cc = "1.0.79" bindgen = "0.65.1" [dependencies] git 引入 submodule cd wrapper_secp256k1 git submodule add https://github.com/bitcoin-core/secp256k1 wrapper_secp256k1/secp256k1_sys 工程下新建bindings目录用来存放绑定文件,该目录与src平级 wrapper.h #include "secp256k1_sys/secp256k1/include/secp256k1.h" build.rs use std::path::PathBuf; fn main() { println!("cargo:rustc-link-lib=secp256k1"); println!("cargo:rerun-if-changed=wrapper.h"); let bindings = bindgen::Builder::default() .header("wrapper.h") .parse_callbacks(Box::new(bindgen::CargoCallbacks)) .generate() .expect("Unable to generate bindings"); let out_path = PathBuf::from("bindings"); bindings .write_to_file(out_path.join("bindings.rs")) .expect("Couldn't write bindings!"); } cargo build 通过 编写测试 lib.rs include!("../bindings/secp256k1.rs"); #[cfg(test)] mod tests { use super::*; #[test] fn test_create_pubkey() { // secp256k1返回公钥 let mut pubkey: secp256k1_pubkey = secp256k1_pubkey { data: [0; 64] }; let prikey: u8 = 1; unsafe { let context = secp256k1_context_create(SECP256K1_CONTEXT_SIGN); assert!(!context.is_null()); let ret = secp256k1_ec_pubkey_create(&*context, &mut pubkey, &prikey); assert_eq!(ret, 1); } } } 运行测试 cargo test 报错 warning: `wrapper_secp256k1` (lib) generated 5 warnings error: linking with `cc` failed: exit status: 1 | = note: LC_ALL="C" PATH="/usr/local/Cellar/rust/1.70.0/lib/rustlib/x86_64-apple-darwin/bin:/Users/jiashiwen/local/Cellar/rust/1.70.0/lib/rustlib/x86_64-apple-darwin/lib/libobject-6d1da0e5d7930106.rlib" "/usr/local/Cellar/rust/1.70.0/lib/rustlib/x86_64-apple-darwin/lib/libmemchr-d6d74858e37ed726.rlib" "/usr/local/Cellar/rust/1.70.0/lib/rustlib/x86_64-apple-darwin/lib/libaddr2line-d75e66c6c1b76fdd.rlib" "/usr/local/Cellar/rust/1.70.0/lib/rustlib/x86_64-apple-darwin/lib/libgimli-546ea342344e3761.rlib" "/usr/local/Cellar/rust/1.70.0/lib/rustlib/x86_64-apple-darwin/lib/librustc_demangle-8ad10e36ca13f067.rlib" "/usr/local/Cellar/rust/1.70.0/lib/rustlib/x86_64-apple-darwin/lib/libstd_detect-0543b8486ac00cf6.rlib" "/usr/local/Cellar/rust/1.70.0/lib/rustlib/x86_64-apple-darwin/lib/libhashbrown-7f0d42017ce08763.rlib" "/usr/local/Cellar/rust/1.70.0/lib/rustlib/x86_64-apple-darwin/lib/libminiz_oxide-65e6b9c4725e3b7f.rlib" "/usr/local/Cellar/rust/1.70.0/lib/rustlib/x86_64-apple-darwin/lib/libadler-131157f72607aea7.rlib" "/usr/local/Cellar/rust/1.70.0/lib/rustlib/x86_64-apple-darwin/lib/librustc_std_workspace_alloc-f7d15060b16c135d.rlib" "/usr/local/Cellar/rust/1.70.0/lib/rustlib/x86_64-apple-darwin/lib/libunwind-a52bfac5ae872be2.rlib" "/usr/local/Cellar/rust/1.70.0/lib/rustlib/x86_64-apple-darwin/lib/libcfg_if-1762d9ac100ea3e7.rlib" "/usr/local/Cellar/rust/1.70.0/lib/rustlib/x86_64-apple-darwin/lib/liblibc-f8e0e4708f61f3f4.rlib" "/usr/local/Cellar/rust/1.70.0/lib/rustlib/x86_64-apple-darwin/lib/liballoc-af9a608dd9cb26b2.rlib" "/usr/local/Cellar/rust/1.70.0/lib/rustlib/x86_64-apple-darwin/lib/librustc_std_workspace_core-9777023438fd3d6a.rlib" "/usr/local/Cellar/rust/1.70.0/lib/rustlib/x86_64-apple-darwin/lib/libcore-83ca6d61eb70e9b8.rlib" "/usr/local/Cellar/rust/1.70.0/lib/rustlib/x86_64-apple-darwin/lib/libcompiler_builtins-ea2ca6e1df0449b8.rlib" "-lSystem" "-lc" "-lm" "-L" "/usr/local/Cellar/rust/1.70.0/lib/rustlib/x86_64-apple-darwin/lib" "-o" "/Users/jiashiwen/rustproject/wrapper_secp256k1/target/debug/deps/wrapper_secp256k1-4bf30c62ecfdf2a7" "-Wl,-dead_strip" "-nodefaultlibs" = note: ld: library not found for -lsecp256k1 clang: error: linker command failed with exit code 1 (use -v to see invocation) warning: `wrapper_secp256k1` (lib test) generated 5 warnings (5 duplicates) error: could not compile `wrapper_secp256k1` (lib test) due to previous error; 5 warnings emitted 报错显示找不到编译 secp256k1 相对应的库。 手动编译secp256k1 cd secp256k1_sys ./autogen.sh ./configure make make install 编译完成后,测试通过 其实 secp256k1 有对应的 rust wrapper,我们这里只是展示一下封装的过程。 wrapper_secp256k1 工程的完整代码位置,有兴趣的朋友可以clone https://github.com/jiashiwen/wenpanrust。通过以下操作查看运行结果: clone 项目 git clone https://github.com/jiashiwen/wenpanrust cd wenpanrust update submodule git submodule init git submodule update 编译 secp256k1 cd wrapper_secp256k1/secp256k1_sys ./autogen.sh ./configure make make install run test cargo test -p wrapper_secp256k1 参考资料 Rust FFI (C vs Rust)学习杂记.pdfbindgen官方文档Rust FFI 编程 - bindgen 使用示例 作者:京东科技 贾世闻 来源:京东云开发者社区

优秀的个人博客,低调大师

文盘Rust -- 用Tokio实现简易任务池

作者:京东科技 贾世闻 Tokio 无疑是 Rust 世界中最优秀的异步Runtime实现。非阻塞的特性带来了优异的性能,但是在实际的开发中我们往往需要在某些情况下阻塞任务来实现某些功能。 我们看看下面的例子 fn main(){ let max_task = 1; let rt = runtime::Builder::new_multi_thread() .worker_threads(max_task) .build() .unwrap(); rt.block_on(async { println!("tokio_multi_thread "); for i in 0..100 { println!("run {}", i); tokio::spawn(async move { println!("spawn {}", i); thread::sleep(Duration::from_secs(2)); }); } }); } 我们期待的运行结构是通过异步任务打印出99个 “spawn i",但实际输出的结果大概这样 tokio_multi_thread run 0 run 1 run 2 ....... run 16 spawn 0 run 17 ...... run 99 spawn 1 spawn 2 ...... spawn 29 ...... spawn 58 spawn 59 59执行完后面就没有输出了,如果把max_task设置为2,情况会好一点,但是也没有执行完所有的异步操作,也就是说在资源不足的情况下,Tokio会抛弃某些任务,这不符合我们的预期。那么能不能再达到了某一阀值的情况下阻塞一下,不再给Tokio新的任务呢。这有点类似线程池,当达达最大线程数的时候阻塞后面的任务待有释放的线程后再继续。 我们看看下面的代码。 fn main(){ let max_task = 2; let rt = runtime::Builder::new_multi_thread() .worker_threads(max_task) .enable_time() .build() .unwrap(); let mut set = JoinSet::new(); rt.block_on(async { for i in 0..100 { println!("run {}", i); while set.len() >= max_task { set.join_next().await; } set.spawn(async move { sleep().await; println!("spawn {}", i); }); } while set.len() > 0 { set.join_next().await; } }); } 我们使用JoinSet来管理派生出来的任务。set.join_next().await; 保证至少一个任务被执行完成。结合set的len,我们可以在任务达到上限时阻塞任务派生。当循环结束,可能还有未完成的任务,所以只要set.len()大于0就等待任务结束。 输出大概长这样 running 1 test tokio_multi_thread run 0 run 1 spawn 0 run 2 spawn 1 ...... run 31 spawn 30 run 32 spawn 31 run 33 ...... run 96 spawn 95 run 97 spawn 96 run 98 spawn 97 run 99 spawn 98 spawn 99 符合预期,代码不多,有兴趣的同学可以动手尝试一下。

优秀的个人博客,低调大师

文盘Rust -- 本地库引发的依赖冲突

作者:京东科技 贾世闻 问题描述 clickhouse 的原生 rust 客户端目前比较好的有两个clickhouse-rs 和 clickhouse.rs 。clickhouse-rs 是 tcp 连接;clickhouse.rs 是 http 连接。两个库在单独使用时没有任何问题,但是,在同一工程同时引用时会报错。 Cargo.toml# clickhouse http clickhouse = {git = "https://github.com/loyd/clickhouse.rs", features = ["test-util"]} # clickhouse tcp clickhouse-rs = { git = "https://github.com/suharev7/clickhouse-rs", features = ["default"]} 报错如下 Blocking waiting for file lock on package cache Updating git repository `https://github.com/suharev7/clickhouse-rs` Updating crates.io index error: failed to select a version for `clickhouse-rs-cityhash-sys`. ... required by package `clickhouse-rs v1.0.0-alpha.1 (https://github. com/suharev7/clickhouse-rs#ecf28f46)` ... which satisfies git dependency `clickhouse-rs` of package `conflict v0.1.0 (/Users/jiashiwen/rustproject/conflict)` versions that meet the requirements `^0.1.2` are: 0.1.2 the package `clickhouse-rs-cityhash-sys` links to the native library `clickhouse-rs`, but it conflicts with a previous package which links to `clickhouse-rs` as well: package `clickhouse-rs-cityhash-sys v0.1.2` ... which satisfies dependency `clickhouse-rs-cityhash-sys = "^0.1.2"` (locked to 0.1.2) of package `clickhouse v0.11.2 (https://github.com/ loyd/clickhouse.rs#4ba31e65)` ... which satisfies git dependency `clickhouse` (locked to 0.11.2) of package `conflict v0.1.0 (/Users/jiashiwen/rustproject/conflict)` Only one package in the dependency graph may specify the same links value. This helps ensure that only one copy of a native library is linked in the final binary. Try to adjust your dependencies so that only one package uses the links ='clickhouse-rs-cityhash-sys' value. For more information, see https://doc.rust-lang.org/cargo/reference/resolver.html#links. failed to select a version for `clickhouse-rs-cityhash-sys` which could resolve this conflict 错误描述还是很清楚的,clickhouse-rs-cityhash-sys 这个库冲突了。仔细看了一下两个库的源码,引用 clickhouse-rs-cityhash-sys 库的方式是不一样的。clickhouse.rs 是在其Cargo.toml 文件中使用最普遍的方式引用的 clickhouse-rs-cityhash-sys = { version = "0.1.2", optional = true } clickhouse-rs 是通过本地方式引用的 [dependencies.clickhouse-rs-cityhash-sys] path = "clickhouse-rs-cityhash-sys" version = "0.1.2" clickhouse-rs-cityhash-sys 的源码直接放在 clickhouse-rs 工程目录下面。 一开始是有个直观的想法,如果在一个工程中通过workspace 进行隔离,是不是会解决冲突问题呢? 于是,工程的目录结构从这样 . ├── Cargo.lock ├── Cargo.toml └── src └── main.rs 改成了这样 . ├── Cargo.lock ├── Cargo.toml ├── ck_http │ ├── Cargo.toml │ └── src ├── ck_tcp │ ├── Cargo.toml │ └── src └── src └── main.rs 新建了两个lib cargo new ck_http --lib cargo new ck_tcp --lib 在 workspace 中分别应用 clickhouse-rs 和 clickhouse.rs ,删除根下 Cargo.toml 文件中的依赖关系。 很可惜,workspace 没有解决问题,报错没有一点儿差别。 又仔细看了看报错,里面有这样一段 the package `clickhouse-rs-cityhash-sys` links to the native library `clickhouse-rs`, but it conflicts with a previous package which links to `clickhouse-rs` 难道是 clickhouse-rs 这个名字冲突了? 直接把clickhouse-rs源码拉下来作为本地库来试试呢? 于是把 clickhouse-rs clone 到本地,稍稍修改一下ck_tcp workspace 的 Cargo.toml clickhouse-rs = { path = "../../clickhouse-rs", features = ["default"]} 编译后冲突依旧存在。 翻翻 clickhouse-rs/clickhouse-rs-cityhash-sys/Cargo.toml,里面的一个配置很可疑 [package] ... ... links = "clickhouse-rs" 把 links 随便改个名字比如:links = "ck-rs-cityhash-sys",编译就通过了。 错误提示中这句话很重要 Only one package in the dependency graph may specify the same links value. 看了一下 links 字段的含义 The links field The links field specifies the name of a native library that is being linked to. More information can be found in the links section of the build script guide. links 指定了本地包被链接的名字,在这里引起了冲突,改掉本地包中的名字自然解决了冲突,在依赖图中保证唯一性很重要。 本文涉及代码github仓库,有兴趣的同学可以亲自试一试 下期见。

优秀的个人博客,低调大师

文盘Rust -- rust 连接云上数仓

作者:京东科技 贾世闻 最近想看看 rust 如何集成 clickhouse,又犯了好吃懒做的心理(不想自己建环境),刚好我东发布了兼容ck 的云原生数仓 Starwfit,于是搞了个实例折腾一番,看看 rust 如何与 starwift 打交道。 创建集群 登录控制台,直接选配创建即可 创建用户 开启白名单 为了保证starwift能够被外部资源访问需要开启白名单,默认情况下新创建的实例只有该实例所在vpc下才能访问 为了方便,实验环境直接编辑 default 分组,编辑 ip 列表为 0.0.0.0/0,对所有ip开放。在实际生产环境需要根据实际情况配置白名单避免安全问题。 开启公网连接 为了实验方便,开启公网访问域名便于开发和测试 mac客户端安装 安装客户端 macos 客户端安装 curl -O 'https://builds.clickhouse.com/master/macos/clickhouse' && chmod a +x ./clickhouse 其他客户端安装 参见 clickhosue 官方文档 测试连通性 clickhouse client --host service-terrabase-9s29mdlsb7. terrabase-9s29mdlsb7-hb-public.jvessel2.jdcloud.com \ --port 9000 \ --user sample \ --password xxxxxx clickhouse:) show databases rust 连接 starwift starwift 支持 tcp 和 http 两种协议连接server。下面我们分别给出两种方式的示例代码 tcp 连接 依赖 crate # clickhouse tcp tokio = { version = "1.21.2", features = ["full"] } clickhouse-rs = { git = "https://github.com/suharev7/clickhouse-rs", features = ["default"]} 示例代码 use clickhouse_rs::Pool; #[tokio::main] async fn main() { let database_url="tcp://username:password@service-terrabase-9s29mdlsb7.terrabase-9s2mdsb-hb-public.jvessel2.jdcloud.com:9000?compression=lz4".to_string(); let pool = Pool::new(database_url); let mut client = pool.get_handle().await.unwrap(); let sql = "show databases;"; let r = client.query(sql).fetch_all().await; println!("result is: {:?}", r); } http 连接 依赖 crate # clickhouse http clickhouse = {git = "https://github.com/loyd/clickhouse.rs", features = ["test-util"]} 示例代码 use clickhouse::Client; use clickhouse::Row; use serde::{Deserialize, Serialize}; #[derive(Debug, Row, Serialize, Deserialize)] struct Database { name: String, } #[tokio::main] async fn main() { let client = Client::default() .with_url("https://service-terrabase-9s29mdlsb7.terrabase-9sdlb7-hb-public.jvessel2.jdcloud.com:8123") .with_user("username") .with_password("password"); let sql = "SHOW databases"; let r = client.query(sql).fetch_all::<Database>().await; println!("result is: {:?}", r); } 测试过程中,遇到了依赖冲突问题,clickhouse-rs 和 clickhouse.rs 同时依赖了 clickhouse-rs-cityhash-sys 但依赖方式不同,导致编译不通过,下一期详细聊聊踩坑及爬坑过程。 再次感谢京东云数据库团队提供的 starwift 测试实例。 下期见。

优秀的个人博客,低调大师

文盘Rust -- 把程序作为守护进程启动

当我们写完一个服务端程序,需要上线部署的时候,或多或少都会和操作系统的守护进程打交道,毕竟谁也不希望shell关闭既停服。今天我们就来聊聊这个事儿。 最早大家部署应用的通常操作是 “nohup xxxx &”,别说像weblogic 或者其他java 容器有启动脚本,里面其实也差不多;很喜欢 nginx的 -d 参数,或者像redis 配置文件里可以指定是否以守护进程启动。看起来很优雅。 那么,使用rust 写一个服务端程序能不能优雅的使用一个参数指定应用 daemon 模式启动,同时使用stop 方式优雅的停机呢?我们通过一个例子来说说基本的实现方式。 实例代码依然集成在[interactcli-rs](https://github.com/jiashiwen/interactcli-rs)工程中。 首先来模拟一个启动的服务进程 /src/server/server.rs ```rust pub fn start(prefix: String) { for i in 0..1000 { println!("{}", prefix.clone() + &i.to_string()); thread::sleep(Duration::from_secs(1)); } } ``` 程序每秒输出一个字符串,持续999秒,这个时间足够验证实验结果了。 后台启动有两个实现,分别是利用[fork](github.com/immortal/fork) 或 [daemonize](github.com/knsd/daemonize),这两个crate 实现原理类似,但在使用上稍有不同。 /src/cmd/cmdserver.rs,构建了两个启动子命令,分别来调用 fork 和 daemonize的守护进程启动实现。 ```rust pub fn new_server_cmd() -> Command { clap::Command::new("server") .about("server") .subcommand(server_start_byfork()) .subcommand(server_start_bydaemonize()) } pub fn server_start_byfork() -> Command { clap::Command::new("byfork") .about("start daemon by fork crate") .arg( Arg::new("daemon") .short('d') .long("daemon") .action(ArgAction::SetTrue) .help("start as daemon") .required(false), ) } pub fn server_start_bydaemonize() -> Command { clap::Command::new("bydaemonize") .about("start daemon by daemonize crate") .arg( Arg::new("daemon") .short('d') .long("daemon") .action(ArgAction::SetTrue) .help("start as daemon") .required(false), ) } ``` server 的子命令 byfork 启动 通过 fork 实现的功能,bydaemonize 则调用通过 daemonize 的功能实现。 命令解析的代码在 /src/cmd/rootcmd.rs 文件中。 先来看看基于 fork 的实现 ```rust if let Some(startbyfork) = server.subcommand_matches("byfork") { println!("start by fork"); if startbyfork.get_flag("daemon") { let args: Vec<String> = env::args().collect(); if let Ok(Fork::Child) = daemon(true, false) { // 启动子进程 let mut cmd = Command::new(&args[0]) for idx in 1..args.len() { let arg = args.get(idx).expect("get cmd arg error!"); // 去除后台启动参数,避免重复启动 if arg.eq("-d") || arg.eq("-daemon") { continue; } cmd.arg(arg); let child = cmd.spawn().expect("Child process failed to start."); fs::write("pid", child.id().to_string()).unwrap(); println!("process id is:{}", std::process::id()); println!("child id is:{}", child.id()); } println!("{}", "daemon mod"); process::exit(0); } start("by_fork:".to_string()); } ``` 首先,通过 Fork::daemon 函数派生出一个子进程;然后解析一下当前命令,去掉 -d 参数,构建一个启动命令,子命令启动,退出父进程。这基本符合操作系统创建守护进程的过程 -- 两次 fork。 再来看看基于 daemonize 的实现。 ```rust if let Some(startbydaemonize) = server.subcommand_matches("bydaemonize") { println!("start by daemonize"); let base_dir = env::current_dir().unwrap(); if startbydaemonize.get_flag("daemon") { let stdout = File::create("/tmp/daemon.out").unwrap(); let stderr = File::create("/tmp/daemon.err").unwrap(); println!("{:?}", base_dir); let daemonize = Daemonize::new() .pid_file("/tmp/test.pid") // Every method except `new` and `start` .chown_pid_file(true) // is optional, see `Daemonize` documentation .working_directory(base_dir.as_path()) // for default behaviour. .umask(0o777) // Set umask, `0o027` by default. .stdout(stdout) // Redirect stdout to `/tmp/daemon.out`. .stderr(stderr) // Redirect stderr to `/tmp/daemon.err`. .privileged_action(|| "Executed before drop privileges"); match daemonize.start() { Ok(_) => { println!("Success, daemonized"); } Err(e) => eprintln!("Error, {}", e), } } println!("pid is:{}", std::process::id()); fs::write("pid", process::id().to_string()).unwrap(); start("by_daemonize:".to_string()); } ``` 首先获取当前的工作目录,默认情况下 daemonize 会将工作目录设置为 "/",为了避免权限问题,我们获取当前目录作为守护进程的工作目录。不知道是什么原因,在配置了pid_file 后,启动守护进程时并没在文件中有记录 pid。不过也没关系,我们可以在外部获取并记录守护进程的pid。 两种方式启动的守护进程均可在关闭shell的情况下维持进程运行。 从实现上来讲,不论是 fork 还是 daemonize 都是 通过unsafe 方式调用了 libc api,类 unix 系统大多跑起来没问题,windows 系统作者没有验证。 本期关于守护进程的话题就聊到这儿。 咱们下期见。 作者:贾世闻

优秀的个人博客,低调大师

文盘Rust -- struct 中的生命周期

最近在用rust 写一个redis的数据校验工具。redis-rs中具备 redis::ConnectionLike trait,借助它可以较好的来抽象校验过程。在开发中,不免要定义struct 中的某些元素为 trait object,从而带来一些rust语言中的生命周期问题。 本文不具体讨论 redis的数据校验过程,通过一个简单的例子来聊聊 struct 中 trait object 元素的生命周期问题。 首先来定义一个 base trait,该 trait 中只包含一个函数,返回String类型。 pub trait Base { fn say(&self) -> String; } 接下来,定义两个实现了 Base trait 的 struct AFromBase 和 BFromBase pub struct AFromBase { content: String, } impl Base for AFromBase { fn say(&self) -> String { self.content.clone() } } pub struct BFromBase { text: String, } impl Base for BFromBase { fn say(&self) -> String { self.text.clone() } } 接下来,定义一个struct 包含两个 Base trait 的 trait object ,然后实现一个函数是 say 函数输出的字符串的拼接结果. 按照其他没有生命周期语言的编写习惯,直觉上这么写 pub struct AddTowBase { a: &mut dyn Base, b: &mut dyn Base, } impl AddTowBase { fn add(&self) -> String { let result = self.a.say() + &self.b.say(); result } } 最后,搞个main函数验证一下。 完整代码如下 pub trait Base { fn say(&self) -> String; } pub struct AFromBase { content: String, } impl Base for AFromBase { fn say(&self) -> String { self.content.clone() } } pub struct BFromBase { text: String, } impl Base for BFromBase { fn say(&self) -> String { self.text.clone() } } pub struct AddTowBase { a: &mut dyn Base, b: &mut dyn Base, } impl<'a> AddTowBase<'a> { fn add(&self) -> String { let result = self.a.say() + &self.b.say(); result } } fn main() { let mut a = AFromBase { content: "baseA".to_string(), }; let mut b = BFromBase { text: "baseB".to_string(), }; let addtow = AddTowBase { a: &mut a, b: &mut b, }; let r = addtow.add(); println!("{}", r); } 很遗憾,以上代码是不能编译通过的,编译时报如下错误 error[E0106]: missing lifetime specifier --> examples/lifetimeinstruct.rs:26:8 | 26 | a: &mut dyn Base, | ^ expected named lifetime parameter | help: consider introducing a named lifetime parameter | 25 ~ pub struct AddTowBase<'a> { 26 ~ a: &'a mut dyn Base, | error[E0106]: missing lifetime specifier --> examples/lifetimeinstruct.rs:27:8 | 27 | b: &mut dyn Base, | ^ expected named lifetime parameter | help: consider introducing a named lifetime parameter | 25 ~ pub struct AddTowBase<'a> { 26 | a: &mut dyn Base, 27 ~ b: &'a mut dyn Base, | For more information about this error, try `rustc --explain E0106`. error: could not compile `wenpan-rust` due to 2 previous errors 编译器给出的提示很明确,要在 trait object 上添加生命周期参数,确保 struct 和他的 trait object 元素在同一生命周期,避免悬垂指针。 我们按照编译器的提示修改代码 pub struct AddTowBase<'a> { a: &'a mut dyn Base, b: &'a mut dyn Base, } impl<'a> AddTowBase<'a> { fn add(self) -> String { let result = self.a.say() + &self.b.say(); result } } 代码顺利通过编译。 rust 的生命周期保证了内存的安全性,同时也增加了开发者的心智负担。是在上线之前多费心思写代码,还是在上线以后忙忙活活查问题,这是个 trade off 问题。俗话讲:"背着抱着,一样沉".我本人还是倾向于把问题控制在上线之前,少折腾用户。 本期咱们先聊到这儿,下期见

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册