├── guide ├── net.md ├── deployment-AWS.md ├── deployment-Azure.md ├── deployment-Linode.md ├── deployment-Heroku.md ├── deployment.md ├── profiling.md ├── cluster.md ├── databaseConnectors.md ├── ai.md ├── UUID.md ├── StORMLifecycleEvents.md ├── StORM-Cursor.md ├── Markdown.md ├── utilities.md ├── env.md ├── ini.md ├── starting-services.md ├── log.md ├── bytes.md ├── repeater.md ├── introduction.md ├── StORM-Insert.md ├── repositoryLayout.md ├── logRemote.md ├── StORM-Update.md ├── MongoDB-Database.md ├── MongoDB-Client.md ├── Hadoop.md ├── formData.md ├── GoogleAnalytics.md ├── handlingRequests.md ├── webRedirects.md ├── Redis.md ├── logFiles.md ├── deployment-Ubuntu.md ├── StORM-MongoDB.md ├── fileUploads.md ├── HTTPRequestLogging.md ├── staticFileContent.md ├── StORM-CouchDB.md ├── sysProcess.md ├── deployment-Docker.md ├── dir.md ├── csrf.md ├── zip.md ├── StORM-Setting-up-a-class.md ├── toc_dev.json └── cors.md ├── guide.zh_CN ├── net.md ├── deployment-AWS.md ├── deployment-Azure.md ├── deployment-Linode.md ├── deployment-Heroku.md ├── deployment.md ├── profiling.md ├── databaseConnectors.md ├── cluster.md ├── utilities.md ├── UUID.md ├── ai.md ├── StORM-Cursor.md ├── StORMLifecycleEvents.md ├── Markdown.md ├── handlingRequests.md ├── env.md ├── log.md ├── ini.md ├── repositoryLayout.md ├── bytes.md ├── StORM-Insert.md ├── repeater.md ├── starting-services.md ├── formData.md ├── MongoDB-Client.md ├── logRemote.md ├── MongoDB-Database.md ├── StORM-Update.md ├── introduction.md ├── webRedirects.md ├── GoogleAnalytics.md ├── Hadoop.md ├── staticFileContent.md ├── StORM-CouchDB.md ├── StORM-MongoDB.md ├── StORM.md ├── logFiles.md ├── deployment-Ubuntu.md ├── csrf.md ├── fileUploads.md ├── Redis.md ├── deployment-Docker.md ├── cors.md ├── dir.md ├── sysProcess.md ├── HTTPRequestLogging.md ├── StORM-Setting-up-a-class.md ├── zip.md ├── SPNEGO.md ├── StORM-Saving-Retrieving-and-Deleting-Rows.md ├── MongoDB.md ├── StORM-SQLite.md ├── WebServicesPrimer.md ├── OAuth2.md ├── StORM-MySQL.md ├── StORM-PostgreSQL.md ├── toc_dev.json ├── python.md ├── HTTPRequest.md ├── HTTPResponse.md └── SMTP.md ├── .gitignore └── api-docs ├── img ├── dash.png ├── gh.png └── carat.png ├── docsets ├── PerfectLib.tgz └── PerfectLib.docset │ └── Contents │ ├── Resources │ ├── docSet.dsidx │ └── Documents │ │ ├── img │ │ ├── dash.png │ │ ├── gh.png │ │ └── carat.png │ │ └── js │ │ └── jazzy.js │ └── Info.plist └── js └── jazzy.js /guide/net.md: -------------------------------------------------------------------------------- 1 | # Networking 2 | ... 3 | 4 | -------------------------------------------------------------------------------- /guide.zh_CN/net.md: -------------------------------------------------------------------------------- 1 | # Networking 2 | ... 3 | 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .Trashes 3 | *.swp 4 | 5 | -------------------------------------------------------------------------------- /guide/deployment-AWS.md: -------------------------------------------------------------------------------- 1 | # AWS Deployment 2 | ... 3 | 4 | -------------------------------------------------------------------------------- /guide.zh_CN/deployment-AWS.md: -------------------------------------------------------------------------------- 1 | # AWS Deployment 2 | ... 3 | 4 | -------------------------------------------------------------------------------- /guide/deployment-Azure.md: -------------------------------------------------------------------------------- 1 | # Azure Deployment 2 | ... 3 | 4 | -------------------------------------------------------------------------------- /guide/deployment-Linode.md: -------------------------------------------------------------------------------- 1 | # Linode Deployment 2 | ... 3 | 4 | -------------------------------------------------------------------------------- /guide.zh_CN/deployment-Azure.md: -------------------------------------------------------------------------------- 1 | # Azure Deployment 2 | ... 3 | 4 | -------------------------------------------------------------------------------- /guide.zh_CN/deployment-Linode.md: -------------------------------------------------------------------------------- 1 | # Linode Deployment 2 | ... 3 | 4 | -------------------------------------------------------------------------------- /guide/deployment-Heroku.md: -------------------------------------------------------------------------------- 1 | # Heroku 2 | Heroku for Perfect. 3 | 4 | -------------------------------------------------------------------------------- /guide.zh_CN/deployment-Heroku.md: -------------------------------------------------------------------------------- 1 | # Heroku 2 | Heroku for Perfect. 3 | 4 | -------------------------------------------------------------------------------- /guide/deployment.md: -------------------------------------------------------------------------------- 1 | # Deployment Options 2 | Deployment options for Perfect. 3 | 4 | -------------------------------------------------------------------------------- /guide.zh_CN/deployment.md: -------------------------------------------------------------------------------- 1 | # Deployment Options 2 | Deployment options for Perfect. 3 | 4 | -------------------------------------------------------------------------------- /api-docs/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PerfectlySoft/PerfectDocs/HEAD/api-docs/img/dash.png -------------------------------------------------------------------------------- /api-docs/img/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PerfectlySoft/PerfectDocs/HEAD/api-docs/img/gh.png -------------------------------------------------------------------------------- /api-docs/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PerfectlySoft/PerfectDocs/HEAD/api-docs/img/carat.png -------------------------------------------------------------------------------- /api-docs/docsets/PerfectLib.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PerfectlySoft/PerfectDocs/HEAD/api-docs/docsets/PerfectLib.tgz -------------------------------------------------------------------------------- /guide.zh_CN/profiling.md: -------------------------------------------------------------------------------- 1 | # 服务器性能远程监控 2 | 3 | 在部署生产服务器后,服务器性能远程监控是现代云计算管理的关键手段之一。Perfect 提供了远程监控服务器的强大能力,详见以下文档: 4 | 5 | * [New Relic](NEWRELIC.md) 6 | -------------------------------------------------------------------------------- /api-docs/docsets/PerfectLib.docset/Contents/Resources/docSet.dsidx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PerfectlySoft/PerfectDocs/HEAD/api-docs/docsets/PerfectLib.docset/Contents/Resources/docSet.dsidx -------------------------------------------------------------------------------- /api-docs/docsets/PerfectLib.docset/Contents/Resources/Documents/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PerfectlySoft/PerfectDocs/HEAD/api-docs/docsets/PerfectLib.docset/Contents/Resources/Documents/img/dash.png -------------------------------------------------------------------------------- /api-docs/docsets/PerfectLib.docset/Contents/Resources/Documents/img/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PerfectlySoft/PerfectDocs/HEAD/api-docs/docsets/PerfectLib.docset/Contents/Resources/Documents/img/gh.png -------------------------------------------------------------------------------- /api-docs/docsets/PerfectLib.docset/Contents/Resources/Documents/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PerfectlySoft/PerfectDocs/HEAD/api-docs/docsets/PerfectLib.docset/Contents/Resources/Documents/img/carat.png -------------------------------------------------------------------------------- /guide.zh_CN/databaseConnectors.md: -------------------------------------------------------------------------------- 1 | # 数据库连接器 2 | 3 | 任何服务器端应用程序的核心都是数据库访问。Perfect支持一系列流行的数据源如下: 4 | 5 | * [SQLite](SQLite.md) 6 | * [MySQL](MySQL.md) 7 | * [PostgreSQL](PostgreSQL.md) 8 | * [MongoDB](MongoDB.md) 9 | * [FileMaker](filemaker.md) 10 | -------------------------------------------------------------------------------- /guide/profiling.md: -------------------------------------------------------------------------------- 1 | # Performance Profiling & Remote Monitoring 2 | 3 | Once server deployed, one of the key cloud based management is remote monitoring. Perfect provides powerful controls over Swift Servers, see documents below for more information: 4 | 5 | * [New Relic](NEWRELIC.md) 6 | -------------------------------------------------------------------------------- /guide.zh_CN/cluster.md: -------------------------------------------------------------------------------- 1 | # 消息队列和服务器集群 2 | 3 | Perfect 提供了一组丰富的服务器扩展功能,用于实时网络消息队列控制,以及服务器集群集中配置。 4 | 5 | ## [Perfect Kafka](Kafka.md) 6 | 7 | Kafka服务器的Swift函数库 8 | 9 | ## [Perfect Mosquitto](mosquitto.md) 10 | 11 | 支持MQTT v3.0协议的实时网络消息队列函数库。 12 | 13 | ## [Perfect Zookeeper](ZooKeeper.md) 14 | 15 | 支持Linux服务器集群集中配置的专用函数库。 -------------------------------------------------------------------------------- /guide.zh_CN/utilities.md: -------------------------------------------------------------------------------- 1 | # 基本工具 2 | 3 | 除了提供基于web的互联网服务函数库之外,Perfect还提供一系列用于搭建服务器和客户机应用程序所需要的一系列工具类基本函数库。 4 | 5 | 如同之前章节讨论的JSON库函数一样,其实本工具库的许多函数完全可以通过Swift语言自带的函数进行实现;但是Perfect的这一组API接口目标是提升最终用户的编程效率、提高程序可读性、简洁性 6 | 7 | 请根据需要查看下列章节的Perfect工具类函数库详细信息: 8 | 9 | * [字节流转换](bytes.md) 10 | * [文件操作](file.md) 11 | * [目录与路径](dir.md) 12 | * [线程](thread.md) 13 | * [UUID唯一标识符](UUID.md) 14 | * [SysProcess系统进程](sysProcess.md) 15 | * [日志](log.md) 16 | * [CURL联网传输](cURL.md) 17 | * [Zip压缩](zip.md) 18 | -------------------------------------------------------------------------------- /guide/cluster.md: -------------------------------------------------------------------------------- 1 | # Message Queue and Clustered Servers 2 | 3 | Perfect provides a rich set of server extensions for large scaled computation in terms of real time message streaming and centralized configuration. 4 | 5 | ## [Perfect Kafka](Kafka.md) 6 | 7 | Swift library for Kafka Server Connectivity. 8 | 9 | ## [Perfect Mosquitto](mosquitto.md) 10 | 11 | MQTT v3.0 compatible real time message queue API. 12 | 13 | ## [Perfect Zookeeper](ZooKeeper.md) 14 | 15 | A Linux Swift Library of clustered servers centralized configuration. -------------------------------------------------------------------------------- /guide.zh_CN/UUID.md: -------------------------------------------------------------------------------- 1 | # UUID唯一识别符 2 | UUID识别码(也被成为全球唯一识别码GUID)是一个128位整数,用于唯一识别一个对象或实体。UUID通过不同部件代码组合来确保其唯一性。 3 | 4 | ### 创建一个新的UUID对象 5 | 6 | 一个新UUID对象可以通过随机数自动创建,或者根据一个现有UUID代码进行指定。 7 | 8 | 如果需要随机创建一个v4版本的UUID: 9 | 10 | ``` swift 11 | let u = UUID() 12 | ``` 13 | 14 | 如果需要从一个字符串中读取并对一个v4版本UUID对象进行赋值: 15 | 16 | ``` swift 17 | let u = UUID() 18 | ``` 19 | 20 | 如果字符串无效,则新创建的UUID对象内容会变为:`00000000-0000-0000-0000-000000000000` 21 | 22 | 如果希望以字符串形式输出UUID的值: 23 | 24 | ``` swift 25 | let u1 = UUID() 26 | print(u1.string) 27 | ``` 28 | -------------------------------------------------------------------------------- /guide.zh_CN/ai.md: -------------------------------------------------------------------------------- 1 | # 大数据和机器学习 2 | 3 | Perfect 在大数据和人工智能/人工神经网络和机器学习方面提供了丰富的功能支持。 4 | 5 | ## [Perfect-TensorFlow](tensorflow.md) 6 | 7 | Perfect TensorFlow 能够允许用户在服务器端加载预先训练好的神经网络模型,在历史数据的基础上进行智能预测。 8 | 9 | ## [Perfect-Hadoop](Hadoop.md) 10 | 11 | Perfect Hadoop 用于目前最流行的大数据服务器之一——Hadoop的服务器端连接控制: 12 | 13 | ### [HDFS](HadoopWebHDFS.md) 14 | ### [MapReduce Master](HadoopMapReduceMaster.md) 15 | ### [MapReduce History](HadoopMapReduceHistory.md) 16 | ### [YARN Node Manager](HadoopYARNNodeManager.md) 17 | ### [YARN Resource Manager](HadoopYARNResourceManager.md) -------------------------------------------------------------------------------- /guide/databaseConnectors.md: -------------------------------------------------------------------------------- 1 | # Database Connectors 2 | 3 | At the heart of any server side application is database access. Perfect supplies a number of connectors to popular datasources such as: 4 | 5 | * [SQLite](https://github.com/PerfectlySoft/PerfectDocs/blob/master/guide/SQLite.md) 6 | * [MySQL](https://github.com/PerfectlySoft/PerfectDocs/blob/master/guide/MySQL.md) 7 | * [PostgreSQL](https://github.com/PerfectlySoft/PerfectDocs/blob/master/guide/PostgreSQL.md) 8 | * [MongoDB](https://github.com/PerfectlySoft/PerfectDocs/blob/master/guide/MongoDB.md) 9 | * [FileMaker](https://github.com/PerfectlySoft/PerfectDocs/blob/master/guide/filemaker.md) 10 | 11 | -------------------------------------------------------------------------------- /guide.zh_CN/StORM-Cursor.md: -------------------------------------------------------------------------------- 1 | # StORM 记录游标 2 | 3 | 在数据库理论定义中,游标代表在当前数据环境,也就是数据库交互时返回的结果记录集(一个数据表)中,当前操作对应的行列位置。 4 | 5 | 使用 StORM 时,游标会返回结果记录集的总行数,并指向期望的行数位置。 6 | 7 | 执行游标设定操作后,StORM会响应总记录行数,但是返回的是实际查询的结果记录集行数。 8 | 9 | 实际应用中,这意味着,您可以设置希望返回的结果数量,并将游标设置为从实际返回的结果记录行作为开始的行号: 10 | 11 | ``` swift 12 | let thisCursor = StORMCursor( 13 | limit: 50, 14 | offset: 100 15 | ) 16 | ``` 17 | 18 | 作为数据行的响应,行号(即上面的offset偏移量)和总记录数会被刷新和回响。通过这种方法您可以实现数据结果查询分页。 19 | 20 | ``` swift 21 | print(thisCursor.limit) 22 | // 50 23 | print(thisCursor.offset) 24 | // 100 25 | print(thisCursor.totalRecords) 26 | // 1045 27 | ``` 28 | 29 | 该游标对象会被传递给`.select`查询方法,并将StORM 实现的数据类对象以结果记录集方式返回。 30 | -------------------------------------------------------------------------------- /guide.zh_CN/StORMLifecycleEvents.md: -------------------------------------------------------------------------------- 1 | # StORM 全局全周期事件 2 | 3 | 7 | 8 | ## modifyValue 9 | 10 | ```Swift 11 | /* Signature: */ open func modifyValue(_: Any, forKey: String) -> Any 12 | ``` 13 | 14 | 该事件在调用 `asData(_:)` 和 `asDataDict(_:)` 过程中会被触发,允许数据自定义编码。 15 | 16 | 举例: 17 | 18 | ```Swift 19 | override func modifyValue(_ v: Any, forKey k: String) -> Any { 20 | if let d = v as? Date { 21 | return d.timestamptz 22 | } 23 | return v 24 | } 25 | ``` 26 | 27 | 该操作会将所有日期型变量转换为字符串变量,其格式为 PostgreSQL's `timestamp with timezone` 的带时区时间戳格式。 28 | 29 | ( `timestamptz` 格式无关紧要,重点是返回的字符串) 30 | -------------------------------------------------------------------------------- /api-docs/docsets/PerfectLib.docset/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | com.jazzy.perfectlib 7 | CFBundleName 8 | PerfectLib 9 | DocSetPlatformFamily 10 | perfectlib 11 | isDashDocset 12 | 13 | dashIndexFilePath 14 | index.html 15 | isJavaScriptEnabled 16 | 17 | DashDocSetFamily 18 | dashtoc 19 | 20 | 21 | -------------------------------------------------------------------------------- /guide/ai.md: -------------------------------------------------------------------------------- 1 | # Big Data & Machine Learning 2 | 3 | Perfect also provides a rich extensions in Big Data / Artificial Intelligence / Artificial Neural Network / Machine Learning. 4 | 5 | ## [Perfect-TensorFlow](tensorflow.md) 6 | 7 | Perfect TensorFlow enables user to load pre-trained AI models to make intelligent predictions based on historic data input. 8 | 9 | ## [Perfect-Hadoop](Hadoop.md) 10 | 11 | Perfect Hadoop is a connectivity library to one of the most popular big data servers: Hadoop: 12 | 13 | ### [HDFS](HadoopWebHDFS.md) 14 | ### [MapReduce Master](HadoopMapReduceMaster.md) 15 | ### [MapReduce History](HadoopMapReduceHistory.md) 16 | ### [YARN Node Manager](HadoopYARNNodeManager.md) 17 | ### [YARN Resource Manager](HadoopYARNResourceManager.md) -------------------------------------------------------------------------------- /guide/UUID.md: -------------------------------------------------------------------------------- 1 | # UUID 2 | Also known as a Globally Unique Identifier (GUID), a Universal Unique Identifier (UUID) is a 128-bit number used to uniquely identify some object or entity. The UUID relies upon a combination of components to ensure uniqueness. 3 | 4 | ### Create a New UUID object 5 | 6 | A new UUID object can either be randomly generated, or assigned. 7 | 8 | To randomly generate a v4 UUID: 9 | 10 | ``` swift 11 | let u = UUID() 12 | ``` 13 | 14 | To assign a v4 UUID from a string: 15 | 16 | ``` swift 17 | let u = UUID() 18 | ``` 19 | 20 | If the string is invalid, the object is assigned the following UUID instead: `00000000-0000-0000-0000-000000000000` 21 | 22 | To return the string value of a UUID: 23 | 24 | ``` swift 25 | let u1 = UUID() 26 | print(u1.string) 27 | ``` 28 | -------------------------------------------------------------------------------- /guide/StORMLifecycleEvents.md: -------------------------------------------------------------------------------- 1 | # StORM Global Lifecycle Events 2 | 6 | 7 | ## modifyValue 8 | ```Swift 9 | /* Signature: */ open func modifyValue(_: Any, forKey: String) -> Any 10 | ``` 11 | 12 | This event is fired during calls to `asData(_:)` and `asDataDict(_:)` to allow custom encoding of the data used. 13 | 14 | Example: 15 | ```Swift 16 | override func modifyValue(_ v: Any, forKey k: String) -> Any { 17 | if let d = v as? Date { 18 | return d.timestamptz 19 | } 20 | return v 21 | } 22 | ``` 23 | This will convert all `Date`s to `String`s formatted as PostgreSQL's `timestamp with timezone` type. 24 | (The implementation of `timestamptz` is irrelevant other than it returns a `String`) 25 | -------------------------------------------------------------------------------- /guide.zh_CN/Markdown.md: -------------------------------------------------------------------------------- 1 | # Perfect-Markdown 2 | 3 | 该项目提供了在Swift中直接从Markdown文本生成HTML的方法 4 | 5 | 该软件使用SPM进行编译和测试,本软件也是[Perfect](https://github.com/PerfectlySoft/Perfect)项目的一部分,但也可以独立使用。 6 | 7 | 请确保您已经安装并激活了最新版本的 Swift 4.0 tool chain 工具链。 8 | 9 | ## 致谢 10 | 11 | Perfect-Markdown 直接基于 [GerHobbelt 的 "upskirt(超短裙)"](https://github.com/GerHobbelt/upskirt) 项目. 12 | 13 | 14 | ## 使用说明 15 | 16 | 请首先修改您的 Package.swift 文件增加依存关系: 17 | 18 | ``` swift 19 | .Package(url: "https://github.com/PerfectlySoft/Perfect-Markdown.git", majorVersion: 3) 20 | ``` 21 | 22 | ## 引用库函数 23 | 24 | 请将下列头文件增加到源代码 25 | 26 | ``` swift 27 | import PerfectMarkdown 28 | ``` 29 | 30 | ## 从 Markdown 文本中获取 HTML 字符串 31 | 32 | 一旦引用成功,String 类型会增加一个名为 `markdownToHTML` 的扩展属性: 33 | 34 | ``` 35 | let markdown = "# 这是一个 markdown 文档 \n\n## with mojo 🇨🇳 🇨🇦" 36 | 37 | guard let html = markdown.markdownToHTML else { 38 | // 转换失败 39 | }//end guard 40 | 41 | print(html) 42 | ``` 43 | 44 | -------------------------------------------------------------------------------- /guide.zh_CN/handlingRequests.md: -------------------------------------------------------------------------------- 1 | # 处理HTTP请求 2 | 作为互联网服务器,Perfect的主要功能是从客户端浏览器接收请求并响应。Perfect提供一系列代表请求和响应的对象组件,并允许在服务器上增加管理句柄用于产生页面内容。 3 | 4 | 所有对象都是在服务器对象创建后开始工作。服务器对象会被执行配置,随后会根据配置绑定并监听特定端口。一旦出现连接,服务器会读取请求数据,请求数据读取完成后,服务器会将[request object请求对象](HTTPRequest.md)传递给请求过滤器。 5 | 6 | 过滤器可能会根据需要修改查询请求。服务器会使用请求的URI路径检索[routing请求/响应路由](routing.md)以获取处理该请求的具体句柄。如果找到了合适的处理句柄,服务器会传递给句柄对应的[response object响应对象](HTTPResponse.md)。当句柄反馈响应完成时,响应对象会被传递给响应过滤器。这些过滤器会根据需要修改最终输出的数据内容。最后响应结果数据会被推送给客户端浏览器,而客户端到服务器的连接或者被关闭、或者被拒绝维持HTTP持久连接、或者为后续请求和响应维持HTTP活动连接。 7 | 8 | 上述过程的详细解释请参考以下文献: 9 | 10 | * [Routing请求/响应路由](routing.md)——描述了HTTP请求/响应之间的路由系统,以及如何安装URL处理句柄 11 | * [HTTPRequest请求](HTTPRequest.md)——提供HTTP请求对象的完整协议 12 | * [HTTPResponse响应](HTTPResponse.md)——提供HTTP响应对象的完整协议 13 | * [HTTP请求与响应过滤器](filters.md)——说明如何增加、如何使用过滤器 14 | 15 | 此外,以下内容描述了如何预制页面内容、为特定任务而定制处理句柄: 16 | 17 | * [Static File Handler静态页面文件](staticFileContent.md)——描述了如何管理静态文件内容 18 | * [Mustache模板](mustache.md)——描述了如何管理和应用Mustache模板页面 19 | -------------------------------------------------------------------------------- /guide.zh_CN/env.md: -------------------------------------------------------------------------------- 1 | # 环境变量 2 | 3 | ## 使用方法 4 | 5 | 首先导入PerfectLib: 6 | 7 | ``` swift 8 | import PerfectLib 9 | ``` 10 | 11 | 现在可以在程序中直接调用`Env`类对象操作环境变量: 12 | 13 | ### 设置 14 | 15 | - 设置一个环境变量 16 | 17 | 以下操作等同于 bash 命令 "export foo=bar" 18 | 19 | ``` swift 20 | Env.set("foo", value: "bar") 21 | ``` 22 | 23 | - 设置一组环境变量 24 | 25 | 同样可以使用字典方式设置一组环境变量 26 | 27 | ``` swift 28 | Env.set(["foo":"bar", "koo":"kar"]) 29 | // 结果等同于 bash 命令 "export foo=bar && export koo=kar" 30 | ``` 31 | 32 | ### 读取 33 | 34 | - 查询单个变量: 35 | 36 | ``` swift 37 | guard let foo = Env.get("foo") else { 38 | // 查询失败 39 | } 40 | ``` 41 | 42 | - 查询单个变量,并附加默认值(如果不存在这个变量就用默认值代替) 43 | 44 | ``` swift 45 | guard let foo = Env.get("foo", defaultValue: "bar") else { 46 | // 既然有默认值,则查询应该不会失败 47 | } 48 | ``` 49 | 50 | - 查询所有系统变量 51 | 52 | ``` swift 53 | let all = Env.get() 54 | // 结果是一个字典 [String: String] 55 | ``` 56 | 57 | ### 删除 58 | 59 | - 删除一个环境变量: 60 | 61 | 62 | ``` swift 63 | Env.del("foo") 64 | ``` -------------------------------------------------------------------------------- /guide.zh_CN/log.md: -------------------------------------------------------------------------------- 1 | # 日志 2 | 3 | Perfect包含了一个内建的错误日志系统。用户可以调用log进行日志分级记录。每个级别的日志都可以输出到命令行或者系统日志。 4 | 5 | 内建的日志警告级别包括(依严重级别依次递增排序): 6 | 7 | * **debug:** 调试 `[DBG]` 8 | * **info:** 信息 `[INFO]` 9 | * **warning:** 警告 `[WARN]` 10 | * **error:** 错误 `[ERR]` 11 | * **critical:** 严重错误 `[CRIT]` 12 | * **terminal:** 服务终止 `[TERM]` 13 | 14 | ### 如果需要将日志信息直接输出到命令行 15 | 16 | ``` swift 17 | Log.debug(message: "程序第123行: value \(myVar)") 18 | Log.info(message: "程序第123行:") 19 | Log.warning(message: "调用错误句柄") 20 | Log.error(message: "满足错误条件\(errorMessage)") 21 | Log.critical(message: "发现异常:\(exceptionVar)") 22 | Log.terminal(message: "异常失控,服务终止。\(infoVar)") 23 | ``` 24 | 25 | ### 如果需要将日志信息输出到系统日志 26 | 27 | 如果需要将所有日志结果输出到系统日志中去,请在程序启动的配置过程中调用`SysLogger()`并设置`Log.logger`属性。一旦设置完成,所有日志将输出到系统日志文件,同时在命令行中同步显示。 28 | 29 | ``` swift 30 | Log.logger = SysLogger() 31 | ``` 32 | 33 | 如果您希望将日志渠道停止输出到系统并返回到命令行,请随时调用上述方法将属性设置回`ConsoleLogger()` 34 | 35 | ``` swift 36 | Log.logger = ConsoleLogger() 37 | ``` 38 | -------------------------------------------------------------------------------- /guide.zh_CN/ini.md: -------------------------------------------------------------------------------- 1 | # Perfect INI File Parser 2 | 3 | 本项目是一个简单的[INI文件](http://baike.baidu.com/item/ini文件)解析器。 4 | 5 | 本项目采用Swift 4 工具链中的SPM软件包管理器编译,是[Perfect](https://github.com/PerfectlySoft/Perfect) 项目的一部分,但也可以作为独立模块使用。 6 | 7 | ## 快速上手 8 | 9 | 配置 Package.swift 文件: 10 | 11 | ``` swift 12 | .Package(url: "https://github.com/PerfectlySoft/Perfect-INIParser.git", majorVersion: 3) 13 | ``` 14 | 15 | 导入函数库: 16 | 17 | ``` swift 18 | import INIParser 19 | ``` 20 | 21 | 加载文件并解析为`INIParser` 对象: 22 | 23 | ``` swift 24 | let ini = try INIParser("/path/to/somefile.ini") 25 | ``` 26 | 27 | 这样就可以读取具体的变量了。 28 | 29 | ### 分节变量: 30 | 31 | 对于多数常规分节变量来说,可以使用该对象的`sections`属性进行读取,比如对于某节内容下的变量: 32 | 33 | ``` 34 | [GroupA] 35 | myVariable = myValue 36 | ``` 37 | 38 | 此时使用语句 `let v = ini.sections["[GroupA]"]?["myVariable"]` 可以得到字符串值 `"myValue"`. 39 | 40 | ### 无章节变量 41 | 42 | 但对于某些INI文件中不存在具体的分节,而是把所有变量都放在了一起,比如: 43 | 44 | ``` 45 | freeVar1 = 1 46 | ``` 47 | 48 | 此时,调用匿名章节属性 `anonymousSection`即可获取变量值: 49 | 50 | ``` 51 | let v = ini.anonymousSection["freeVar1"] 52 | ``` 53 | -------------------------------------------------------------------------------- /guide.zh_CN/repositoryLayout.md: -------------------------------------------------------------------------------- 1 | # 代码资源库结构 2 | Perfect的整个软件框架包含了数个组成部分,分别存储在不同的代码资源库上,便于您为您的项目查找,下载和安装所需的组件: 3 | 4 | * [Perfect](https://github.com/PerfectlySoft/Perfect)——该代码资源库包含了PerfectLib库核心,也是整个软件框架的核心内容 5 | * [PerfectTemplate](https://github.com/PerfectlySoft/PerfectTemplate)——项目模板,一个使用Swift Package Manager软件包管理器编译的、可以单独执行的HTTP服务器。该项目模板非常适合开发基于Perfect的服务器项目 6 | * [PerfectDocs](https://github.com/PerfectlySoft/PerfectDocs)——包含了所有API参考资料 7 | * [PerfectExamples](https://github.com/PerfectlySoft/PerfectExamples)——所有Perfect项目典型示例和文档 8 | * [Perfect-Redis](https://github.com/PerfectlySoft/Perfect-Redis)——Redis数据库连接工具 9 | * [Perfect-SQLite](https://github.com/PerfectlySoft/Perfect-SQLite)——SQLite3数据库连接工具 10 | * [Perfect-PostgreSQL](https://github.com/PerfectlySoft/Perfect-PostgreSQL)——PostgreSQL数据库连接工具 11 | * [Perfect-MySQL](https://github.com/PerfectlySoft/Perfect-MySQL)——MySQL数据库连接工具 12 | * [Perfect-MongoDB](https://github.com/PerfectlySoft/Perfect-MongoDB)——MongoDB数据库连接工具 13 | * [Perfect-FastCGI-Apache2.4](https://github.com/PerfectlySoft/Perfect-FastCGI-Apache2.4)——Apache 2.4 FastCGI模块;对于Perfect FastCGI服务器应用是必须安装的内容 14 | 15 | 所有数据库连接工具都是独立的,可以在Perfect框架之外独立使用。 16 | -------------------------------------------------------------------------------- /guide/StORM-Cursor.md: -------------------------------------------------------------------------------- 1 | # StORM Cursor 2 | 3 | In database terms, a cursor defines the size and location of the returned rows within the context of the complete found set. 4 | 5 | For a request using StORM, the cursor governs the number of rows returned and the position of the cursor in the total possible result set. 6 | 7 | For the response, this information is echoed but the total number of possible rows in the found set is also populated. 8 | 9 | Practically, this means that for a request you set the number of rows and the offset from the first record that you wish to retrieve: 10 | 11 | ``` swift 12 | let thisCursor = StORMCursor( 13 | limit: 50, 14 | offset: 100 15 | ) 16 | ``` 17 | 18 | And in response the number of rows, the offset position and the total count is echoed back. This allows you to calculate pagination through the found set as required. 19 | 20 | ``` swift 21 | print(thisCursor.limit) 22 | // 50 23 | print(thisCursor.offset) 24 | // 100 25 | print(thisCursor.totalRecords) 26 | // 1045 27 | ``` 28 | 29 | This cursor object is passed to a `.select` method, and returned as part of all objects using the database-specific StORM class inheritance. -------------------------------------------------------------------------------- /guide.zh_CN/bytes.md: -------------------------------------------------------------------------------- 1 | # 字节流转换 2 | 3 | Bytes二进制字节流对象是在一个无符号8位整型数组基础上为实现Swift简单流操作而开发的类。该对象支持对所有无符号整型值的互相转换:UInt8、UInt16、UInt32和UInt64。在转换这些数值的流操作过程中,新增内容会在容器数组末端进行追加操作。在导出的流操作过程中,会有一个标明当前数据流位置信息的指示器用于检查进度。该对象是PerfectLib的一个组成部分,如果需要使用Bytes对象,则请在程序开通采用`import PerfectLib`声明库函数调用 4 | 5 | 该二进制字节流对象的主要设计目的是用于二进制数据在网络上传输过程中的打包和解包(备注:互联网传输协议与本地计算机协议直接存在高低位字节顺序转换问题——译者注)。用于表达结果的8位无符号数组可以通过访问`data`属性实现。 6 | 7 | 以下例子说明了不同大小不同内容的流数据导入导出以及验证的过程,同时也说明了流位置指示器是如何说明在导出流还有多少字节待操作。 8 | 9 | ``` swift 10 | let i8 = 254 as UInt8 11 | let i16 = 54045 as UInt16 12 | let i32 = 4160745471 as UInt32 13 | let i64 = 17293541094125989887 as UInt64 14 | 15 | let bytes = Bytes() 16 | 17 | bytes.import64Bits(from: i64) 18 | .import32Bits(from: i32) 19 | .import16Bits(from: i16) 20 | .import8Bits(from: i8) 21 | 22 | let bytes2 = Bytes() 23 | bytes2.importBytes(from: bytes) 24 | 25 | XCTAssert(i64 == bytes2.export64Bits()) 26 | XCTAssert(i32 == bytes2.export32Bits()) 27 | XCTAssert(i16 == bytes2.export16Bits()) 28 | bytes2.position -= sizeof(UInt16.self) 29 | XCTAssert(i16 == bytes2.export16Bits()) 30 | XCTAssert(bytes2.availableExportBytes == 1) 31 | XCTAssert(i8 == bytes2.export8Bits()) 32 | ``` 33 | -------------------------------------------------------------------------------- /api-docs/js/jazzy.js: -------------------------------------------------------------------------------- 1 | window.jazzy = {'docset': false} 2 | if (typeof window.dash != 'undefined') { 3 | document.documentElement.className += ' dash' 4 | window.jazzy.docset = true 5 | } 6 | if (navigator.userAgent.match(/xcode/i)) { 7 | document.documentElement.className += ' xcode' 8 | window.jazzy.docset = true 9 | } 10 | 11 | // On doc load, toggle the URL hash discussion if present 12 | $(document).ready(function() { 13 | if (!window.jazzy.docset) { 14 | var linkToHash = $('a[href="' + window.location.hash +'"]'); 15 | linkToHash.trigger("click"); 16 | } 17 | }); 18 | 19 | // On token click, toggle its discussion and animate token.marginLeft 20 | $(".token").click(function(event) { 21 | if (window.jazzy.docset) { 22 | return; 23 | } 24 | var link = $(this); 25 | var animationDuration = 300; 26 | $content = link.parent().parent().next(); 27 | $content.slideToggle(animationDuration); 28 | 29 | // Keeps the document from jumping to the hash. 30 | var href = $(this).attr('href'); 31 | if (history.pushState) { 32 | history.pushState({}, '', href); 33 | } else { 34 | location.hash = href; 35 | } 36 | event.preventDefault(); 37 | }); 38 | -------------------------------------------------------------------------------- /guide/Markdown.md: -------------------------------------------------------------------------------- 1 | # Perfect-Markdown 2 | 3 | 4 | This project provides a solution to convert markdown text into html presentations. 5 | 6 | This package builds with Swift Package Manager and is part of the [Perfect](https://github.com/PerfectlySoft/Perfect) project but can also be used as an independent module. 7 | 8 | ## Acknowledgement 9 | 10 | Perfect-Markdown is directly building on [GerHobbelt's "upskirt"](https://github.com/GerHobbelt/upskirt) project. 11 | 12 | 13 | ## Swift Package Manager 14 | 15 | Add dependencies to your Package.swift 16 | 17 | ``` swift 18 | .Package(url: "https://github.com/PerfectlySoft/Perfect-Markdown.git", majorVersion: 3) 19 | ``` 20 | 21 | ## Import Perfect Markdown Library 22 | 23 | Add the following header to your swift source code: 24 | 25 | ``` swift 26 | import PerfectMarkdown 27 | ``` 28 | 29 | ## Get HTML from Markdown Text 30 | 31 | Once imported, a new String extension `markdownToHTML` would be available: 32 | 33 | ``` 34 | let markdown = "# some blah blah blah markdown text \n\n## with mojo 🇨🇳 🇨🇦" 35 | 36 | guard let html = markdown.markdownToHTML else { 37 | // conversion failed 38 | }//end guard 39 | 40 | print(html) 41 | ``` 42 | -------------------------------------------------------------------------------- /guide.zh_CN/StORM-Insert.md: -------------------------------------------------------------------------------- 1 | # 用 StORM 插入数据 2 | 3 | 除了使用 `.save()` 方法外,StORM 提供了一个更加明确的`.insert`功能用于插入数据。 4 | 5 | 该方法能够用于快速插入大量空数据行而免于逐一设置各数据行属性。 6 | 7 | 另外请注意 StORM 的 `.save` 方法内部是调用 `.insert` 方法的。 8 | 9 | `.insert`的三种使用形式有: 10 | 11 | ``` swift 12 | insert([(String, Any)]) 13 | insert(cols: [String], params: [Any]) 14 | insert(cols: [String], params: [Any], idcolumn: String) 15 | ``` 16 | 17 | 其中, `insert([(String, Any)])` 将根据列名/取值进行逐个插入数据表,这个操作会改变主索引id字段内容并返回字段值。 18 | 19 | `insert(cols: [String], params: [Any])` 操作是一样的,但是输入参数不一样,是两个数组。 20 | 21 | `insert(cols: [String], params: [Any], idcolumn: String)` 允许指定主索引所在列,执行后主索引值将被返回。 22 | 23 | 上述各种情况下只要主索引被确定了,调用时都会把索引值一同返回。 24 | 25 | ## 使用插入方法 26 | 27 | 如果希望插入新一行数据并返回一个自动创建的主索引值,请参考以下样例: 28 | 29 | ``` swift 30 | var obj = User(connect) 31 | obj.id = try obj.insert( 32 | cols: ["firstname","lastname","email"], 33 | params: ["Donkey", "Kong", "donkey.kong@mailinator.com"] 34 | ) as! Int 35 | ``` 36 | 37 | 插入时也可以手工填写主索引的 id 值: 38 | 39 | ``` swift 40 | var obj = User(connect) 41 | obj.id = try obj.insert( 42 | cols: ["id","firstname","lastname","email"], 43 | params: ["10001","Donkey", "Kong", "donkey.kong@mailinator.com"] 44 | ) as! Int 45 | ``` 46 | -------------------------------------------------------------------------------- /guide.zh_CN/repeater.md: -------------------------------------------------------------------------------- 1 | # Perfect Repeater 2 | 3 | 本函数库提供了实现定期运行程序的方法。 4 | 5 | ## 示范代码 6 | 7 | * [Perfect-Repeater-Example](https://github.com/PerfectExamples/Perfect-Repeater-Demo) 8 | 9 | ## 快速上手 10 | 11 | 使用本函数库需要调用PerfectLib模块。此外,请追加Perfect-Repeater模块到您项目的Package.swift文件中去: 12 | 13 | ``` swift 14 | .Package(url:"https://github.com/PerfectlySoft/Perfect-Repeater.git", majorVersion: 3) 15 | ``` 16 | 17 | ## 使用方法 18 | 19 | 首先在源代码中导入函数库: 20 | 21 | ``` swift 22 | import PerfectRepeater 23 | ``` 24 | 25 | 然后您就可以在程序中使用如下定时器: 26 | 27 | ``` swift 28 | Repeater.exec(timer: , callback: ) 29 | ``` 30 | 31 | 其中,`timer`间隔时间,单位是秒。 32 | 33 | `callback` 为期望定期执行的回调函数句柄,返回值必须是布尔类型。您自行定义的这个回调函数句柄的返回值在于,如果返回为真则表示定时器仍然有效,将继续按时调用;否则如果返回假,表示自动停止继续执行,定时器将把该句柄从执行队列中移除。 34 | 35 | 下列代码展示了定时器的使用,并演示了在按期执行若干次后,如何自动停止: 36 | 37 | ``` swift 38 | var opt = 1 39 | 40 | let c = { 41 | () -> Bool in 42 | print("XXXXXX") 43 | return true 44 | } 45 | let cc = { 46 | () -> Bool in 47 | print("你好! (\(opt))") 48 | if opt < 10 { 49 | opt += 1 50 | return true 51 | } else { 52 | print("定时器结束") 53 | return false 54 | } 55 | } 56 | 57 | Repeater.exec(timer: 3.0, callback: c) 58 | Repeater.exec(timer: 2.0, callback: cc) 59 | ``` -------------------------------------------------------------------------------- /guide.zh_CN/starting-services.md: -------------------------------------------------------------------------------- 1 | # Ubuntu 16.04: 系统服务安装指南 2 | 3 | 完成编译的Swift可执行二进制文件可以随时根据需要在Ubuntu上执行;但是对于大多数服务器而言,如Web服务器这样的服务器端应用程序而言,最好以系统服务的方式安装,这样操作系统就可以自动启动并监控这些程序。 4 | 5 | Ubuntu 16.04 用 `systemd` 命令来管理系统服务,本章用于解释如何设置和使用 `systemd`。 6 | 7 | 一旦Swift可执行文件包编译并部署到服务器上之后,请在`/etc/systemd/system/`目录下创建一个`.service` 文件。 8 | 9 | 该文件基本内容样式如下(请自行替换值,注意环境变量可以设置多个,每行一个变量): 10 | 11 | ``` 12 | [Unit] 13 | Description=XXX API Server 14 | 15 | [Service] 16 | Type=simple 17 | WorkingDirectory=/path/to/binary/ 18 | ExecStart=/path/to/binary/APIServer 19 | Restart=always 20 | PIDFile=/var/run/apiserver.pid 21 | Environment="LD_LIBRARY_PATH=/usr/lib:/usr/local/lib:/usr/local/lib/swift" 22 | 23 | [Install] 24 | WantedBy=multi-user.target 25 | ``` 26 | 27 | 文件保存后请设置该文件权限: 28 | 29 | ``` 30 | chmod +x /etc/systemd/system/apiserver.service 31 | chmod 755 /etc/systemd/system/apiserver.service 32 | ``` 33 | 下面的命令能够激活(enable)并启动服务: 34 | 35 | ``` 36 | sudo systemctl enable apiserver.service 37 | sudo systemctl start apiserver.service 38 | ``` 39 | 40 | 关于 `systemd`命令的更多信息请参考[使用 systemd 命令管理服务(英文版)](https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/System_Administrators_Guide/sect-Managing_Services_with_systemd-Services.html) 41 | -------------------------------------------------------------------------------- /api-docs/docsets/PerfectLib.docset/Contents/Resources/Documents/js/jazzy.js: -------------------------------------------------------------------------------- 1 | window.jazzy = {'docset': false} 2 | if (typeof window.dash != 'undefined') { 3 | document.documentElement.className += ' dash' 4 | window.jazzy.docset = true 5 | } 6 | if (navigator.userAgent.match(/xcode/i)) { 7 | document.documentElement.className += ' xcode' 8 | window.jazzy.docset = true 9 | } 10 | 11 | // On doc load, toggle the URL hash discussion if present 12 | $(document).ready(function() { 13 | if (!window.jazzy.docset) { 14 | var linkToHash = $('a[href="' + window.location.hash +'"]'); 15 | linkToHash.trigger("click"); 16 | } 17 | }); 18 | 19 | // On token click, toggle its discussion and animate token.marginLeft 20 | $(".token").click(function(event) { 21 | if (window.jazzy.docset) { 22 | return; 23 | } 24 | var link = $(this); 25 | var animationDuration = 300; 26 | $content = link.parent().parent().next(); 27 | $content.slideToggle(animationDuration); 28 | 29 | // Keeps the document from jumping to the hash. 30 | var href = $(this).attr('href'); 31 | if (history.pushState) { 32 | history.pushState({}, '', href); 33 | } else { 34 | location.hash = href; 35 | } 36 | event.preventDefault(); 37 | }); 38 | -------------------------------------------------------------------------------- /guide.zh_CN/formData.md: -------------------------------------------------------------------------------- 1 | # 使用表单 2 | 3 | 在一个REST应用程序中,经常会使用到几个HTTP“请求动作”。通常是“GET”、“POST” 4 | 5 | >上述请求的详细用法已经超过本文范围。 6 | 7 | 一个HTTP的“GET”请求是通过URL连接中直接以参数方式传递的: 8 | 9 | ``` 10 | http://www.example.com/page.html?message=Hello,%20World! 11 | ``` 12 | 13 | 以上的“GET参数”可以通过`.queryParams`方法来获取 14 | 15 | ``` swift 16 | let params = request.queryParams 17 | ``` 18 | 19 | 以上的例子只适合GET请求,而`.queryParams`方法能够应用到任何一种包含查询参数的HTTP请求。 20 | 21 | ## POST参数 22 | 23 | POST参数是用于在浏览器和服务器之间传递复杂数据的标准方法。 24 | 25 | Perfect的HTTP函数库可以为用户以数组形式访问POST参数提供便利。 26 | 27 | 为了从查询和POST请求中获取所有`[(String,String)]`参数数组,请使用下面的方法: 28 | 29 | ``` swift 30 | let params = request.params() 31 | ``` 32 | 33 | 如果只需要返回POST的`[(String,String)]`参数数组: 34 | 35 | ``` swift 36 | let params = request.postParams() 37 | ``` 38 | 39 | 如果需要根据一个具体名称(比如多个checkbox选项表)返回所有的参数,请使用: 40 | 41 | ``` swift 42 | let params = request.postParams(name: ) 43 | ``` 44 | 45 | 这会返回一个字符串数组:`[String]` 46 | 47 | 如果需要返回一个特定参数,可以输入选择一个可选的`String?`字符串值 48 | 49 | ``` swift 50 | let param = request.param(name: ) 51 | ``` 52 | 53 | 在`request` 对象中整理POST参数时,如果需要填写一个具体参数但是客户端表单并没有按要求填写,此时为该参数设定一个可选的`String?`字符串默认值会非常有用: 54 | 55 | ``` swift 56 | let param = request.param(name: , defaultValue: ) 57 | ``` 58 | -------------------------------------------------------------------------------- /guide.zh_CN/MongoDB-Client.md: -------------------------------------------------------------------------------- 1 | # MongoDB 客户端 2 | 3 | MongoClient客户端类用于初始化到MongoDB服务器的连接。 4 | 5 | 将MongoClient客户端连接到服务器: 6 | 7 | ``` swift 8 | let client = try! MongoClient(uri: "mongodb://localhost") 9 | ``` 10 | 11 | ### 关闭连接 12 | 13 | 一旦连接建立、打开数据库并打开集合,请用`defer`滞后方法关闭连接,注意关闭顺序与建立连接的顺序正好相反——先关闭集合,然后关闭数据库,最后在关闭服务器连接。 14 | 15 | ``` swift 16 | defer { 17 | collection.close() 18 | db.close() 19 | client.close() 20 | } 21 | ``` 22 | 23 | ### 创建数据库引用 24 | 25 | 调用`getDatabase`函数返回当前服务器连接内指定的MongoDatabase。 26 | 27 | ``` swift 28 | let db = client.getDatabase( databaseName: ) 29 | ``` 30 | 31 | #### 参数说明 32 | 33 | * **databaseName:** 字符串类型的数据库名 34 | 35 | ### 创建集合参考引用 36 | 37 | 调用`getCollection`方法能够将客户端连接到当前服务器指定数据库下的目标集合。 38 | 39 | ``` swift 40 | let collection = client.getCollection( databaseName: , collectionName: ) 41 | ``` 42 | 43 | #### 参数说明 44 | 45 | * **databaseName:** 字符串类型的数据库名称 46 | * **collectionName:** 字符串类型的目标集合名称 47 | 48 | ### 获得当前Mongo服务器状态 49 | 50 | 调用`serverStatus`方法返回代表服务器状态的一个对象, 51 | 52 | ``` swift 53 | let status = client.serverStatus() 54 | ``` 55 | 56 | ### 数据库名称列表 57 | 58 | 调用`databaseNames`方法以字符串数组形式获取当前所有可用的数据库名称列表 59 | 60 | 61 | ``` swift 62 | let dbnames = client.databaseNames() 63 | ``` 64 | -------------------------------------------------------------------------------- /guide.zh_CN/logRemote.md: -------------------------------------------------------------------------------- 1 | # 远程日志 2 | 3 | 使用 `PerfectLogger` 模块,本地服务器应用产生的事件可以注册并记录到远程服务器上,也可以用于在控制面板上查看。 4 | 5 | 生产级的日志服务器 [Perfect Log Server](https://github.com/PerfectServers/Perfect-LogServer) 是单独的开源项目,可以在您的服务器上进行部署。 6 | 7 | 8 | ## 使用方法 9 | 10 | 首先请在您的项目中Pacakge.swift 文件增加依存关系: 11 | 12 | ``` swift 13 | .Package(url: "https://github.com/PerfectlySoft/Perfect-Logger.git", majorVersion: 3), 14 | ``` 15 | 16 | 下一步是在程序开始前导入日志记录模块: 17 | 18 | ``` swift 19 | import PerfectLogger 20 | ``` 21 | 22 | ## 配置 23 | 24 | 使用远程日志功能需要配置三个参数: 25 | 26 | ``` swift 27 | // 钥匙代码 28 | RemoteLogger.token = "" 29 | 30 | // 应用程序序号(可选项)App ID 31 | RemoteLogger.appid = "" 32 | 33 | // URL 地址,即远程目标具有日志记录功能的服务器地址 34 | // 请注意,这里没用完整的API路径,仅仅是服务器域名和端口。 35 | RemoteLogger.logServer = "http://localhost:8181" 36 | 37 | ``` 38 | 39 | ## 编程方法 40 | 41 | 如果要将本地事件记录到远程服务器: 42 | 43 | ``` swift 44 | var obj = [String: Any]() 45 | obj["one"] = "donkey" 46 | RemoteLogger.critical(obj) 47 | ``` 48 | 49 | ## 关联事件关系:使用“eventid”事件编号 50 | 51 | 每一条事件都有一个唯一的事件编号。如果在记录事件的指令中附带了另一个事件的编号,则能将当前正在记录的事件与附加事件进行关联: 52 | 53 | ``` swift 54 | let eid = RemoteLogger.critical(obj) 55 | RemoteLogger.info(obj, eventid: eid) 56 | ``` 57 | 58 | 返回的事件编码在程序中明确的标记为 `@discardableResult` 因此您可以忽略这些变量的内存管理,不会对系统造成垃圾堆积。 59 | -------------------------------------------------------------------------------- /guide.zh_CN/MongoDB-Database.md: -------------------------------------------------------------------------------- 1 | # MongoDB 数据库 2 | 3 | MongoDB Database 类用于根据MongoClient客户端实例来访问服务器上的命名数据库。 4 | 5 | 创建一个新的Mongo Database连接: 6 | 7 | ``` swift 8 | let database = try! MongoDatabase( 9 | client: , 10 | databaseName: 11 | ) 12 | ``` 13 | 14 | ### 关闭连接 15 | 16 | 一旦连接建立、打开数据库并打开集合,请用`defer`滞后方法关闭连接,注意关闭顺序与建立连接的顺序正好相反——先关闭集合,然后关闭数据库,最后在关闭服务器连接。 17 | 18 | ``` swift 19 | defer { 20 | collection.close() 21 | db.close() 22 | client.close() 23 | } 24 | ``` 25 | 26 | ### 删除数据库 27 | 28 | 下面的函数可以删除当前数据库并删除所有相关的数据文件。 29 | 30 | ``` swift 31 | database.drop() 32 | ``` 33 | 34 | ### 获得当前数据名 35 | 36 | 调用`name()`函数返回当前数据库名称。 37 | 38 | ``` swift 39 | let name = database.name() 40 | ``` 41 | 42 | ### 创建一个新的集合 43 | 44 | ``` swift 45 | database.createCollection(name: , options: ) 46 | ``` 47 | 48 | #### 参数说明 49 | 50 | * **name:** 用于代表新建集合的字符串名称 51 | * **options:** 新建集合的选项,用BSON文档类型表示 52 | 53 | ### 通过名称创建MongoDB的集合引用 54 | 55 | 调用`getCollection`以创建对一个MongoCollection集合对象的引用: 56 | 57 | 58 | ``` swift 59 | let collection = database.getCollection(name: ) 60 | ``` 61 | 62 | ### 当前数据库集合清单 63 | 64 | 调用`collectionNames`可以字符串数组的形式获得当前数据库内的集合列表: 65 | 66 | 67 | ``` swift 68 | let collection = database.collectionNames() 69 | ``` 70 | -------------------------------------------------------------------------------- /guide/utilities.md: -------------------------------------------------------------------------------- 1 | # Perfect Utilities 2 | 3 | In addition to the core Web Service-related functionality, Perfect provides a range of fundamental building blocks with which to build server side and desktop applications. 4 | 5 | Like the JSON library discussed in previous chapters, many of these functions can be achieved by Swift's provided functions; however, Perfect's APIs aim to increase the efficiency, readability and simplicity of the end user's code. 6 | 7 | Consult the followings sections for detailed information on the utilities available in Perfect's libraries: 8 | 9 | * [Bytes](https://github.com/PerfectlySoft/PerfectDocs/blob/master/guide/bytes.md) 10 | * [File](https://github.com/PerfectlySoft/PerfectDocs/blob/master/guide/file.md) 11 | * [Dir](https://github.com/PerfectlySoft/PerfectDocs/blob/master/guide/dir.md) 12 | * [Threading](https://github.com/PerfectlySoft/PerfectDocs/blob/master/guide/thread.md) 13 | * [UUID](https://github.com/PerfectlySoft/PerfectDocs/blob/master/guide/UUID.md) 14 | * [SysProcess](https://github.com/PerfectlySoft/PerfectDocs/blob/master/guide/sysProcess.md) 15 | * [Log](https://github.com/PerfectlySoft/PerfectDocs/blob/master/guide/log.md) 16 | * [CURL](https://github.com/PerfectlySoft/PerfectDocs/blob/master/guide/cURL.md) 17 | * [Zip](https://github.com/PerfectlySoft/PerfectDocs/blob/master/guide/zip.md) -------------------------------------------------------------------------------- /guide/env.md: -------------------------------------------------------------------------------- 1 | # Environmental Operations 2 | 3 | ## Usage 4 | 5 | First, ensure the `PerfectLib` is imported in your Swift file: 6 | 7 | ``` swift 8 | import PerfectLib 9 | ``` 10 | You are now able to use the `Env` class to operate the environmental variables 11 | 12 | ### Set 13 | 14 | - Single Variable Setting: 15 | 16 | This statement is equal to bash command "export foo=bar" 17 | 18 | ``` swift 19 | Env.set("foo", value: "bar") 20 | ``` 21 | 22 | - Group Setting: 23 | 24 | It is also possible to set a group of variables in a dictionary style: 25 | 26 | ``` swift 27 | Env.set(["foo":"bar", "koo":"kar"]) 28 | // the result is identically the same as "export foo=bar && export koo=kar" 29 | ``` 30 | 31 | ### Get 32 | 33 | - Single variable query: 34 | 35 | ``` swift 36 | guard let foo = Env.get("foo") else { 37 | // there is no such a variable 38 | } 39 | ``` 40 | 41 | - Single variable query with a default value: 42 | 43 | ``` swift 44 | guard let foo = Env.get("foo", defaultValue: "bar") else { 45 | // there is no such a variable even with a default value?? 46 | } 47 | ``` 48 | 49 | - Query all system variables: 50 | 51 | ``` swift 52 | let all = Env.get() 53 | // the result of all is a dictionary [String: String] 54 | ``` 55 | 56 | ### Delete 57 | 58 | - Delete an environmental variable: 59 | 60 | 61 | ``` swift 62 | Env.del("foo") 63 | ``` -------------------------------------------------------------------------------- /guide.zh_CN/StORM-Update.md: -------------------------------------------------------------------------------- 1 | # 使用 StORM 更新数据记录 2 | 3 | 与 StORM 的`.insert`方法一样,可以使用 `.update` 方法对数据对象对应的数据记录进行更新。 4 | 5 | 具体说来,`.update`有两种形式: 6 | 7 | ``` swift 8 | update( 9 | cols: [String], 10 | params: [Any], 11 | idName: String, 12 | idValue: Any 13 | ) 14 | 15 | update( 16 | data: [(String, Any)], 17 | idName: String = "id", 18 | idValue: Any 19 | ) 20 | ``` 21 | 22 | 二者差别只是所用变量形式不同。 23 | 24 | ## 使用 Update 方法 25 | 26 | 请参考以下示例更新数据行: 27 | 28 | ``` swift 29 | let obj = User(connect) 30 | obj.firstname = "Joe" 31 | obj.lastname = "Smith" 32 | 33 | // 首先,创建一行新数据 34 | try obj.save { 35 | id in obj.id = id as! Int 36 | } 37 | 38 | // 然后再改变数据内容 39 | obj.firstname = "Mickey" 40 | obj.lastname = "Mouse" 41 | obj.email = "Mickey.Mouse@mailinator.com" 42 | 43 | try obj.update( 44 | cols: ["firstname","lastname","email"], 45 | params: [obj.firstname, obj.lastname, obj.email], 46 | idName: "id", 47 | idValue: obj.id 48 | ) 49 | ``` 50 | 51 | 上述方法介绍了如何直接更新数据的过程,这种情况下调用`try obj.save()`和 `.update`是同等效率的。 52 | 53 | 而`.update`更有效的使用环境是,在从数据源取得数据记录之前,您就实现已经掌握了主索引记录的值。 54 | 55 | 这种情况下,UPDATE操作将不经任何包装处理,直接连接到数据库完成操作。 56 | 57 | ``` swift 58 | let obj = User(connect) 59 | try obj.update( 60 | cols: ["firstname","lastname","email"], 61 | params: ["Mickey", "Mouse", "Mickey.Mouse@mailinator.com"], 62 | idName: "id", 63 | idValue: 100001 64 | ) 65 | 66 | ``` 67 | -------------------------------------------------------------------------------- /guide.zh_CN/introduction.md: -------------------------------------------------------------------------------- 1 | # Perfect:支持Swift语言服务器端编程的软件函数库 2 | 3 | [![Swift 4.0](https://img.shields.io/badge/Swift-4.0-orange.svg?style=flat)](https://developer.apple.com/swift/) 4 | [![Platforms OS X | Linux](https://img.shields.io/badge/Platforms-OS%20X%20%7C%20Linux%20-lightgray.svg?style=flat)](https://developer.apple.com/swift/) 5 | [![License Apache](https://img.shields.io/badge/License-Apache-lightgrey.svg?style=flat)](http://perfect.org/licensing.html) 6 | [![Docs](https://img.shields.io/badge/docs-99%25-yellow.svg?style=flat)](http://www.perfect.org/docs/) 7 | [![codebeat](https://codebeat.co/badges/85f8f628-6ce8-4818-867c-21b523484ee9)](https://codebeat.co/projects/github-com-perfectlysoft-perfect) 8 | [![Twitter](https://img.shields.io/badge/Twitter-@PerfectlySoft-blue.svg?style=flat)](http://twitter.com/PerfectlySoft) 9 | [![Join the chat at https://gitter.im/PerfectlySoft/Perfect](https://img.shields.io/badge/Gitter-Join%20Chat-brightgreen.svg)](https://gitter.im/PerfectlySoft/Perfect?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 10 | 11 | Perfect是一组完整、强大的工具箱、软件框架体系和Web应用服务器,可以在Linux和macOS (OS X)上使用。该软件体系为Swift工程师量身定制了一整套用于开发轻量、易维护、规模可扩展的Web应用及其它REST服务的解决方案,这样Swift工程师就可以实现同时在服务器和客户端上采用同一种语言开发软件项目。 12 | 13 | 由于建立在一个高性能异步网络引擎基础上,Perfect还能够在FastCGI上运行,支持安全套接字加密(SSL)。该软件体系还包含很多其它互联网服务器所需要的特点,包括WebSockets和iOS消息推送,而且很快会有更多强大的功能支持。 14 | 15 | 无论您是资深程序员还是入门级的软件工程师,本文都能够帮助您快速启动Perfect实现服务器项目开发运行。 16 | -------------------------------------------------------------------------------- /guide.zh_CN/webRedirects.md: -------------------------------------------------------------------------------- 1 | # Web 重定向 2 | 3 | Perfect WebRedirects 模块能够过滤特定路由(包括通配符路由)并按照匹配结果执行请求重定向服务。 4 | 5 | 这一点对于搜索引擎优化而言可能是非常重要的。比如,如果之前有一个静态文件`/about.html`被更新为新的路径`/about`,那么对不起您的网站之前辛辛苦苦积累的搜索引擎排名就全丢了。 6 | 7 | 关于本功能模块的详细用法和展示参考项目可以在这里找到:[Perfect-WebRedirects-Demo](https://github.com/PerfectExamples/Perfect-WebRedirects-Demo). 8 | 9 | ## 使用方法 10 | 11 | 首先在您的项目中Package.swift文件增加依存关系: 12 | 13 | ``` swift 14 | .Package(url: "https://github.com/PerfectlySoft/Perfect-WebRedirects", majorVersion: 3), 15 | ``` 16 | 17 | 然后在您Perfect服务器的路由过滤器配置程序中,比如 `main.swift` 里增加如下过滤器: 18 | 19 | ``` swift 20 | import PerfectWebRedirects 21 | ``` 22 | 23 | 之后就可以追加过滤器了: 24 | 25 | ``` swift 26 | // 在config环节中增加过滤器: 27 | [ 28 | "type":"request", 29 | "priority":"high", 30 | "name":WebRedirectsFilter.filterAPIRequest, 31 | ] 32 | ``` 33 | 34 | 如果您还需要追加请求日志处理器,而且重定向过滤器排在请求处理器的后面,则二者都会被触发响应,新的请求不但会首先记录,而且会被正确的重定向。 35 | 36 | ## 配置文件 37 | 38 | 路由配置文件是一个JSON文件,形式如下:`/config/redirect-rules/*.json` 39 | 40 | ``` 41 | { 42 | 43 | "/test/no": { 44 | "code": 302, 45 | "destination": "/test/yes" 46 | }, 47 | 48 | "/test/no301": { 49 | "code": 301, 50 | "destination": "/test/yes" 51 | }, 52 | 53 | "/test/wild/*": { 54 | "code": 302, 55 | "destination": "/test/wildyes" 56 | }, 57 | 58 | "/test/wilder/*": { 59 | "code": 302, 60 | "destination": "/test/wilding/*" 61 | } 62 | 63 | } 64 | ``` 65 | 66 | 注意该目录下可以存放多个JSON文件;所有该目录下的JSON文件都会在过滤器首次触发时加载。 67 | 68 | 其中,每个过滤器的关键词是原有路径,而值对应的是目标需要重定向的新路径。 -------------------------------------------------------------------------------- /guide/ini.md: -------------------------------------------------------------------------------- 1 | # Perfect INI File Parser 2 | 3 | This project provides an express parser for [INI](https://en.wikipedia.org/wiki/INI_file) files. 4 | 5 | This package builds with Swift Package Manager of Swift 4 Tool Chain and is part of the [Perfect](https://github.com/PerfectlySoft/Perfect) project but can be used as an independent module. 6 | 7 | ## Quick Start 8 | 9 | Configure Package.swift: 10 | 11 | ``` swift 12 | .Package(url: "https://github.com/PerfectlySoft/Perfect-INIParser.git", majorVersion: 3) 13 | ``` 14 | 15 | Import library into your code: 16 | 17 | ``` swift 18 | import INIParser 19 | ``` 20 | 21 | Load the objective INI file by initializing a `INIParser` object: 22 | 23 | ``` swift 24 | let ini = try INIParser("/path/to/somefile.ini") 25 | ``` 26 | 27 | Then it should be possible to access variables inside the file. 28 | 29 | ### Variables with Specific Section 30 | 31 | For most regular lines under a certain section, use `sections` attribute of `INIParser`. Take example: 32 | 33 | ``` 34 | [GroupA] 35 | myVariable = myValue 36 | ``` 37 | 38 | Then `let v = ini.sections["[GroupA]"]?["myVariable"]` will get the value as `"myValue"`. 39 | 40 | ### Variables without Section 41 | 42 | However, some ini files may not have any available sections but directly put all variables together: 43 | 44 | ``` 45 | freeVar1 = 1 46 | ``` 47 | 48 | In this case, call `anonymousSection` to load the corresponding value: 49 | 50 | ``` 51 | let v = ini.anonymousSection["freeVar1"] 52 | ``` 53 | -------------------------------------------------------------------------------- /guide/starting-services.md: -------------------------------------------------------------------------------- 1 | # Ubuntu 16.04: Starting Services at System Boot 2 | 3 | A compiled Swift binary can be run on demand on Ubuntu, however for common server side applications such as API servers, it is best to have the startup and monitoring of state performed by the system automatically. 4 | 5 | Ubuntu 16.04 leverages `systemd` to manage services, and this guide explains how to set up and enable a binary to run under `systemd`. 6 | 7 | Once your Swift binary is placed on the server, you will need to create a `.service` file in the `/etc/systemd/system/` directory. 8 | 9 | The contents of the file will be, at minimum, the following (substituting the obvious values): 10 | 11 | ``` 12 | [Unit] 13 | Description=XXX API Server 14 | 15 | [Service] 16 | Type=simple 17 | WorkingDirectory=/path/to/binary/ 18 | ExecStart=/path/to/binary/APIServer 19 | Restart=always 20 | PIDFile=/var/run/apiserver.pid 21 | 22 | [Install] 23 | WantedBy=multi-user.target 24 | ``` 25 | 26 | Once this file has been saved, set the permissions: 27 | 28 | ``` 29 | chmod +x /etc/systemd/system/apiserver.service 30 | chmod 755 /etc/systemd/system/apiserver.service 31 | ``` 32 | Then enable, and start the service: 33 | 34 | ``` 35 | sudo systemctl enable apiserver.service 36 | sudo systemctl start apiserver.service 37 | ``` 38 | 39 | Further detail about `systemd` can be found at [Managing Services with systemd](https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/System_Administrators_Guide/sect-Managing_Services_with_systemd-Services.html) -------------------------------------------------------------------------------- /guide/log.md: -------------------------------------------------------------------------------- 1 | # Log 2 | 3 | Perfect has a built-in error logging system that allows messages to be logged at several different levels. Each log level can be routed to either the console or the system log. 4 | 5 | The built-in log levels, in order of increasing severity: 6 | 7 | * **debug:** Log lines are preceded by `[DBG]` 8 | * **info:** Log lines are preceded by `[INFO]` 9 | * **warning:** Log lines are preceded by `[WARN]` 10 | * **error:** Log lines are preceded by `[ERR]` 11 | * **critical:** Log lines are preceded by `[CRIT]` 12 | * **terminal:** Log lines are preceded by `[TERM]` 13 | 14 | ### To Log Information to the Console: 15 | 16 | ``` swift 17 | Log.debug(message: "Line 123: value \(myVar)") 18 | Log.info(message: "At Line 123") 19 | Log.warning(message: "Entered error handler") 20 | Log.error(message: "Error condition: \(errorMessage)") 21 | Log.critical(message: "Exception Caught: \(exceptionVar)") 22 | Log.terminal(message: "Uncaught exception, terminating. \(infoVar)") 23 | ``` 24 | 25 | ### To Log Information to the System Log: 26 | 27 | If you wish to pipe all log entries to the system log, set the `Log.logger` property to `SysLogger()` early in the application setup. Once this has been executed, all output will be logged to the System Log file, and echoed to the console. 28 | 29 | ``` swift 30 | Log.logger = SysLogger() 31 | ``` 32 | 33 | If you wish to change the logger process back to only the console at any point, set the property back to `ConsoleLogger()` 34 | 35 | ``` swift 36 | Log.logger = ConsoleLogger() 37 | ``` 38 | -------------------------------------------------------------------------------- /guide.zh_CN/GoogleAnalytics.md: -------------------------------------------------------------------------------- 1 | # Google Analytics Measurement Protocol 2 | 3 | 谷歌网站分析协议函数库相当于网页中的谷歌分析工具在服务器端的实现,也就是说,您可以用日志记录任何一种网络活动——无论是原始的TCP/UDP事件,还是某种被触发的特定事件,比如AJAX。 4 | 5 | ## 编程手册 6 | 7 | 详细编程手册请参考这里: [https://www.perfect.org/docs/api-Perfect-GoogleAnalytics-MeasurementProtocol.html](https://www.perfect.org/docs/api-Perfect-GoogleAnalytics-MeasurementProtocol.html). 8 | 9 | 该手册详细说明了函数库内的所有接口、方法和属性。 10 | 11 | ## 配置方法 12 | 13 | `PerfectGAMeasurementProtocol`结构用于在应用程序运行空间内设置属性编号和点击类型: 14 | 15 | ``` swift 16 | PerfectGAMeasurementProtocol.propertyid = "UA-XXXXXXXX-X" 17 | PerfectGAMeasurementProtocol.hitType = "pageview" 18 | ``` 19 | 20 | ## 编译 21 | 22 | 请在您项目的Package.swift文件中增加如下依存关系: 23 | 24 | ``` swift 25 | .Package(url: "https://github.com/PerfectlySoft/Perfect-GoogleAnalytics-MeasurementProtocol.git", majorVersion: 3) 26 | ``` 27 | 28 | ## 使用范例 29 | 30 | 设置执行和登记某个事件的方法: 31 | 32 | ```swift 33 | PerfectGAMeasurementProtocol.propertyid = "UA-XXXXXXXX-X" 34 | let gaex = PerfectGAEvent() 35 | gaex.user.uid = "donkey" 36 | gaex.user.cid = "kong" 37 | gaex.session.ua = "aua" 38 | gaex.traffic.ci = "ci" 39 | gaex.system.fl = "x" 40 | gaex.hit.ni = 2 41 | 42 | 43 | do { 44 | let str = try gaex.generate() 45 | print(str) 46 | let resp = gaex.makeRequest(useragent: "TestingAPI1.0", body: str) 47 | print(resp) 48 | } catch { 49 | print("\(error)") 50 | } 51 | 52 | ``` 53 | 54 | 常见的点击类型和配置请参考下列文档: 55 | [https://developers.google.com/analytics/devguides/collection/protocol/v1/devguide#commonhits](https://developers.google.com/analytics/devguides/collection/protocol/v1/devguide#commonhits) 56 | 57 | -------------------------------------------------------------------------------- /guide/bytes.md: -------------------------------------------------------------------------------- 1 | # Bytes 2 | 3 | The bytes object provides simple streaming of common Swift values to and from a UInt8 array. It supports importing and exporting UInt8, UInt16, UInt32, and UInt64 values. When importing these values, they are appended to the end of the contained array. When exporting, a repositionable marker is kept indicating the current export location. The bytes object is included as part of PerfectLib. Make sure to ```import PerfectLib``` if you wish to use the Bytes object. 4 | 5 | The primary purpose behind the bytes object is to enable binary network payloads to be easily assembled and decomposed. The resulting UInt8 array is available through the ```data``` property. 6 | 7 | The following example illustrates importing values of various sizes, and then exporting and validating the values. It also shows how to reposition the marker and how to determine how many bytes remain for export. 8 | 9 | ```swift 10 | let i8 = 254 as UInt8 11 | let i16 = 54045 as UInt16 12 | let i32 = 4160745471 as UInt32 13 | let i64 = 17293541094125989887 as UInt64 14 | 15 | let bytes = Bytes() 16 | 17 | bytes.import64Bits(from: i64) 18 | .import32Bits(from: i32) 19 | .import16Bits(from: i16) 20 | .import8Bits(from: i8) 21 | 22 | let bytes2 = Bytes() 23 | bytes2.importBytes(from: bytes) 24 | 25 | XCTAssert(i64 == bytes2.export64Bits()) 26 | XCTAssert(i32 == bytes2.export32Bits()) 27 | XCTAssert(i16 == bytes2.export16Bits()) 28 | bytes2.position -= sizeof(UInt16.self) 29 | XCTAssert(i16 == bytes2.export16Bits()) 30 | XCTAssert(bytes2.availableExportBytes == 1) 31 | XCTAssert(i8 == bytes2.export8Bits()) 32 | ``` 33 | -------------------------------------------------------------------------------- /guide/repeater.md: -------------------------------------------------------------------------------- 1 | # Perfect Repeater 2 | 3 | This package provides a method to schedule repeating/recurring events. 4 | 5 | ## Relevant Examples 6 | 7 | * [Perfect-Repeater-Example](https://github.com/PerfectExamples/Perfect-Repeater-Demo) 8 | 9 | ## Getting Started 10 | 11 | In addition to the PerfectLib, you will need the Perfect-Repeater dependency in the Package.swift file: 12 | 13 | ``` swift 14 | .Package(url:"https://github.com/PerfectlySoft/Perfect-Repeater.git", majorVersion: 3) 15 | ``` 16 | 17 | ## Using Perfect Repeater 18 | 19 | Import the Perfect Repeater into each file that you wish to use the functions in: 20 | 21 | ``` swift 22 | import PerfectRepeater 23 | ``` 24 | 25 | The base form of executing this is: 26 | 27 | ``` swift 28 | Repeater.exec(timer: , callback: ) 29 | ``` 30 | 31 | The `timer` value is the time in seconds to repeat the event. 32 | 33 | The `callback` contains a closure containing code to execute. This must contain a boolean return value. Returning `true` will re-queue the event, and `false` will remove the event from the queue. 34 | 35 | The following code demonstrates the process of repeating a closure containing your code and optionally re-queuing: 36 | 37 | ``` swift 38 | var opt = 1 39 | 40 | let c = { 41 | () -> Bool in 42 | print("XXXXXX") 43 | return true 44 | } 45 | let cc = { 46 | () -> Bool in 47 | print("Hello, world! (\(opt))") 48 | if opt < 10 { 49 | opt += 1 50 | return true 51 | } else { 52 | print("cc exiting.") 53 | return false 54 | } 55 | } 56 | 57 | Repeater.exec(timer: 3.0, callback: c) 58 | Repeater.exec(timer: 2.0, callback: cc) 59 | ``` -------------------------------------------------------------------------------- /guide/introduction.md: -------------------------------------------------------------------------------- 1 | # Perfect: Server-Side Swift 2 | 3 | [![Swift 4.0](https://img.shields.io/badge/Swift-4.0-orange.svg?style=flat)](https://developer.apple.com/swift/) 4 | [![Platforms OS X | Linux](https://img.shields.io/badge/Platforms-OS%20X%20%7C%20Linux%20-lightgray.svg?style=flat)](https://developer.apple.com/swift/) 5 | [![License Apache](https://img.shields.io/badge/License-Apache-lightgrey.svg?style=flat)](http://perfect.org/licensing.html) 6 | [![Docs](https://img.shields.io/badge/docs-99%25-yellow.svg?style=flat)](http://www.perfect.org/docs/) 7 | [![codebeat](https://codebeat.co/badges/85f8f628-6ce8-4818-867c-21b523484ee9)](https://codebeat.co/projects/github-com-perfectlysoft-perfect) 8 | [![Twitter](https://img.shields.io/badge/Twitter-@PerfectlySoft-blue.svg?style=flat)](http://twitter.com/PerfectlySoft) 9 | [![Join the chat at http://perfect.ly](http://perfect.ly/badge.svg)](http://perfect.ly/badge.svg) 10 | 11 | 12 | Perfect is a complete and powerful toolbox, framework, and application server for Linux and macOS. It provides everything a Swift engineer needs for developing lightweight, maintainable, and scalable apps and other REST services entirely in the Swift programming language for both client-facing and server-side applications. 13 | 14 | Built on a high-performance asynchronous networking engine, Perfect can also run on FastCGI, and it supports Secure Sockets Layer encryption. There are many other features including a suite of tools commonly required by internet servers such as WebSockets and iOS push notifications, but you are not limited to those options. 15 | 16 | This guide is designed for developers at all levels of experience to get Perfect up and running quickly. 17 | -------------------------------------------------------------------------------- /guide/StORM-Insert.md: -------------------------------------------------------------------------------- 1 | # Inserting a Row with StORM 2 | 3 | In addition to the `.save()` method, StORM provides access to a more granular set of `.insert` functionality. 4 | 5 | This can be used to rapidly insert larger number of rows without having to populate each property in advance. 6 | 7 | It is also worth noting that StORM's `.save` functions are effectively convenience functions for `.insert`. 8 | 9 | The three "forms" of `.insert` are: 10 | 11 | ``` swift 12 | insert([(String, Any)]) 13 | insert(cols: [String], params: [Any]) 14 | insert(cols: [String], params: [Any], idcolumn: String) 15 | ``` 16 | 17 | `insert([(String, Any)])` will take the name/value pairs and insert them into the table. It will infer the id column and return its value. 18 | 19 | `insert(cols: [String], params: [Any])` performs the same operation, but with different input supplied. 20 | 21 | `insert(cols: [String], params: [Any], idcolumn: String)` allows you to specify the primary key column. The resulting primary key is returned. 22 | 23 | In each case, if the primary key value is specified, a successful insert will echo the supplied value. 24 | 25 | ## Using Insert 26 | 27 | To insert a new row and return the auto-generated primary key: 28 | 29 | ``` swift 30 | var obj = User() 31 | obj.id = try obj.insert( 32 | cols: ["firstname","lastname","email"], 33 | params: ["Donkey", "Kong", "donkey.kong@mailinator.com"] 34 | ) as! Int 35 | ``` 36 | 37 | To insert a new row and return the supplied primary key: 38 | 39 | ``` swift 40 | var obj = User() 41 | obj.id = try obj.insert( 42 | cols: ["id","firstname","lastname","email"], 43 | params: ["10001","Donkey", "Kong", "donkey.kong@mailinator.com"] 44 | ) as! Int 45 | ``` -------------------------------------------------------------------------------- /guide/repositoryLayout.md: -------------------------------------------------------------------------------- 1 | # Repository Layout 2 | 3 | The Perfect framework has been divided into several repositories to make it easy for you to find, download, and install the components you need for your project: 4 | 5 | ## The Perfect Core Library 6 | 7 | [Perfect](https://github.com/PerfectlySoft/Perfect) - This repository contains the core PerfectLib and will continue to be the main landing point for the project 8 | 9 | ## The Perfect Toolkit 10 | 11 | There are many components in the main [Perfect Repo, https://github.com/PerfectlySoft](https://github.com/PerfectlySoft) that make up the comprehensive Perfect Toolkit. There are database drivers, utilities, session management, and authentication systems. All components are documented here. 12 | 13 | ## The Perfect Template 14 | 15 | [PerfectTemplate](https://github.com/PerfectlySoft/PerfectTemplate) - A simple starter project which compiles with the Swift Package Manager into a standalone executable HTTP server. This repository is ideal for starting on your own Perfect-based project 16 | 17 | ## The Perfect Documentation - Open Source 18 | 19 | [PerfectDocs](https://github.com/PerfectlySoft/PerfectDocs) - Contains all API reference-related material 20 | 21 | ## Perfect Examples 22 | 23 | [PerfectExamples](https://github.com/PerfectExamples) - All the Perfect example projects and documentation 24 | 25 | ## StORM - A Swift ORM 26 | 27 | [StORM is a Swift ORM](https://github.com/SwiftORM), written in Perfect. The list of supported databases will continue to grow and mature. 28 | 29 | ## Perfect Servers 30 | 31 | [A collection of standalone servers written in Perfect](https://github.com/PerfectServers), ready for deployment (with a little configuration on your part!) -------------------------------------------------------------------------------- /guide.zh_CN/Hadoop.md: -------------------------------------------------------------------------------- 1 | # PerfectHadoop 2 | 3 | 该项目实现了一个对 WebHDFS 网络接口的封装,用于访问 Hadoop 服务器。 4 | 5 | 该软件使用SPM进行编译和测试,本软件也是[Perfect](https://github.com/PerfectlySoft/Perfect)项目的一部分。本软件包可独立使用,因此使用时可以脱离PerfectLib等其他组件。 6 | 7 | 请确保您已经安装并激活了最新版本的 Swift 4.0 tool chain 工具链。 8 | 9 | ### 问题报告、内容贡献和客户支持 10 | 11 | 我们目前正在过渡到使用JIRA来处理所有源代码资源合并申请、修复漏洞以及其它有关问题。因此,GitHub 的“issues”问题报告功能已经被禁用了。 12 | 13 | 如果您发现了问题,或者希望为改进本文提供意见和建议,[请在这里指出](http://jira.perfect.org:8080/servicedesk/customer/portal/1). 14 | 15 | 在您开始之前,请参阅[目前待解决的问题清单](http://jira.perfect.org:8080/projects/ISS/issues). 16 | 17 | ## 版本兼容性 18 | PerfectHadoop 目前支持 Hadoop 3.0.0,以及 2.7.3 的部分功能。 19 | 20 | ## 编译 21 | 请在您的 Package.swift 文件中增加以下内容 22 | 23 | ``` swift 24 | .Package(url:"https://github.com/PerfectlySoft/Perfect-Hadoop.git", majorVersion: 3) 25 | ``` 26 | 27 | 并在您的源程序部分增加以下函数库声明: 28 | ``` swift 29 | import PerfectHadoop 30 | ``` 31 | ## 错误处理 - `Exception` 32 | 33 | 由于基于REST API,大多数 Perfect-Hadoop 库函数在出错时会抛出一个`Exception`对象,用户可以捕捉该错误并检查三元组`(url, header, body)`如下列程序所示: 34 | 35 | ``` swift 36 | do { 37 | // 执行任何一个 Perfect Hadoop 操作,包括 WebHDFS / MapReduce / YARN,所有的操作 38 | ... 39 | } 40 | catch(Exception.unexpectedResponse(let (url, header, body))) { 41 | print("出现REST API异常: \(url)\n\(header)\n\(body)") 42 | } 43 | catch (let err){ 44 | print("其它错误:\(err)") 45 | } 46 | ``` 47 | 48 | ## 用户手册 49 | - WebHDFS: [Perfect-HDFS](HadoopWebHDFS.md) 50 | - MapReduce: 51 | * [Perfect-MapReduce 应用程序接口 API](HadoopMapReduceMaster.md) ⚠️ 试验版本 ⚠️ 52 | * [Perfect-MapReduce 历史服务器接口 API](HadoopMapReduceHistory.md) 53 | - YARN: 54 | * [Perfect-YARN 节点管理器](HadoopYARNNodeManager.md) 55 | * [Perfect-YARN 资源管理器](HadoopYARNResourceManager.md) 56 | 57 | 58 | 59 | ## 更多信息 60 | 关于本项目更多内容,请参考[perfect.org](http://perfect.org). 61 | -------------------------------------------------------------------------------- /guide/logRemote.md: -------------------------------------------------------------------------------- 1 | # Remote Logging 2 | 3 | Using the `PerfectLogger` module, events can be logged to a specified remote Perfect Log Server, in addition to the console. 4 | 5 | The [Perfect Log Server](https://github.com/PerfectServers/Perfect-LogServer) is a stand-alone project that can be deployed on your own servers. 6 | 7 | 8 | ## Using in your project 9 | 10 | To include the dependency in your project, add the following to your project's Package.swift file: 11 | 12 | ``` swift 13 | .Package(url: "https://github.com/PerfectlySoft/Perfect-Logger.git", majorVersion: 3), 14 | ``` 15 | 16 | Now add the import directive to the file you wish to use the logging in: 17 | 18 | ``` swift 19 | import PerfectLogger 20 | ``` 21 | 22 | ## Configuration 23 | Three configuration parameters are required: 24 | 25 | ``` swift 26 | // Your token 27 | RemoteLogger.token = "" 28 | 29 | // App ID (Optional) 30 | RemoteLogger.appid = "" 31 | 32 | // URL to access the log server. 33 | // Note, this is not the full API path, just the host and port. 34 | RemoteLogger.logServer = "http://localhost:8181" 35 | 36 | ``` 37 | 38 | ## Usage 39 | 40 | To log events to the log server: 41 | 42 | ``` swift 43 | var obj = [String: Any]() 44 | obj["one"] = "donkey" 45 | RemoteLogger.critical(obj) 46 | ``` 47 | 48 | ## Linking events with "eventid" 49 | 50 | Each log event returns an event id string. If an eventid string is supplied to the directive then it will use the supplied eventid in the log directive instead. This makes it easy to link together related events. 51 | 52 | ``` swift 53 | let eid = RemoteLogger.critical(obj) 54 | RemoteLogger.info(obj, eventid: eid) 55 | ``` 56 | 57 | The returned eventid is marked `@discardableResult` and therefore can be safely ignored if not required for re-use. 58 | -------------------------------------------------------------------------------- /guide/StORM-Update.md: -------------------------------------------------------------------------------- 1 | # Updating Rows with StORM 2 | 3 | In a way similar to StORM's `.insert` methods, you can use `.update` methods to specify updates directly. 4 | 5 | The two forms of `.update` available are: 6 | 7 | ``` swift 8 | update( 9 | cols: [String], 10 | params: [Any], 11 | idName: String, 12 | idValue: Any 13 | ) 14 | 15 | update( 16 | data: [(String, Any)], 17 | idName: String = "id", 18 | idValue: Any 19 | ) 20 | ``` 21 | 22 | The only difference between these two is the format of the data supplied. 23 | 24 | ## Using Update 25 | 26 | Let's look at an example of how to create, then update a row: 27 | 28 | ``` swift 29 | let obj = User() 30 | obj.firstname = "Joe" 31 | obj.lastname = "Smith" 32 | 33 | // First, lets create a new row 34 | try obj.save { 35 | id in obj.id = id as! Int 36 | } 37 | 38 | // Now, we change the values 39 | obj.firstname = "Mickey" 40 | obj.lastname = "Mouse" 41 | obj.email = "Mickey.Mouse@mailinator.com" 42 | 43 | try obj.update( 44 | cols: ["firstname","lastname","email"], 45 | params: [obj.firstname, obj.lastname, obj.email], 46 | idName: "id", 47 | idValue: obj.id 48 | ) 49 | ``` 50 | 51 | While the above illustrates the direct update process, in this case calling `try obj.save()` would be leaner and just as efective as the `.update`. 52 | 53 | Where an `.update` would be more effective, is a situation where you know all the information, including the primary key value, before any fetch from the datasource. 54 | 55 | This way, an update would be called directly without any initial round trip connection to the database. 56 | 57 | ``` swift 58 | let obj = User() 59 | try obj.update( 60 | cols: ["firstname","lastname","email"], 61 | params: ["Mickey", "Mouse", "Mickey.Mouse@mailinator.com"], 62 | idName: "id", 63 | idValue: 100001 64 | ) 65 | 66 | ``` -------------------------------------------------------------------------------- /guide/MongoDB-Database.md: -------------------------------------------------------------------------------- 1 | # MongoDB Database 2 | 3 | Use the MongoDB Database class to create a reference to a named database using a provided MongoClient instance. 4 | 5 | Create a new Mongo Database connection: 6 | 7 | ``` swift 8 | let database = try! MongoDatabase( 9 | client: , 10 | databaseName: 11 | ) 12 | ``` 13 | 14 | ### Closing the Connection 15 | 16 | Once the connection is established and the database and collections have been defined, set the connection to close once completed using `defer`. This is done in reverse order: close collections, then databases, then finally the client connection. 17 | 18 | ``` swift 19 | defer { 20 | collection.close() 21 | db.close() 22 | client.close() 23 | } 24 | ``` 25 | 26 | ### Drop the Current Database 27 | 28 | Drops the current database, deleting the associated data files. 29 | 30 | ``` swift 31 | database.drop() 32 | ``` 33 | 34 | ### Current Database Name 35 | 36 | `name()` returns the name of the current database. 37 | 38 | ``` swift 39 | let name = database.name() 40 | ``` 41 | 42 | ### Create a New Collection 43 | 44 | ``` swift 45 | database.createCollection(name: , options: ) 46 | ``` 47 | 48 | #### Parameters 49 | 50 | * **name:** String, name of collection to be created 51 | * **options:** BSON document listing options for new collection 52 | 53 | ### Create Reference to MongoDB Collection Referenced by Name 54 | 55 | Use `getCollection` to create a reference to a MongoCollection: 56 | 57 | 58 | ``` swift 59 | let collection = database.getCollection(name: ) 60 | ``` 61 | 62 | ### Create String Array of Current Database Collections' Names 63 | 64 | Use `collectionNames` to create an array of the databases' collection names: 65 | 66 | 67 | ``` swift 68 | let collection = database.collectionNames() 69 | ``` 70 | 71 | -------------------------------------------------------------------------------- /guide/MongoDB-Client.md: -------------------------------------------------------------------------------- 1 | # MongoDB Client 2 | 3 | The MongoClient class is where the initial connection to the MongoDB server is defined. 4 | 5 | Create a new Mongo client connection: 6 | 7 | ``` swift 8 | let client = try! MongoClient(uri: "mongodb://localhost") 9 | ``` 10 | 11 | ### Closing the Connection 12 | 13 | Once the connection is established and the database and collections have been defined, set the connection to close once completed using `defer`. This is done in reverse order: close collections, then databases, and then finally the client connection. 14 | 15 | ``` swift 16 | defer { 17 | collection.close() 18 | db.close() 19 | client.close() 20 | } 21 | ``` 22 | 23 | ### Create Database Reference 24 | 25 | `getDatabase` returns the specified MongoDatabase using the current connection. 26 | 27 | ``` swift 28 | let db = client.getDatabase( 29 | databaseName: 30 | ) 31 | ``` 32 | 33 | #### Parameters 34 | 35 | * **databaseName:** String name of database to be used 36 | 37 | ### Create Collection Reference 38 | 39 | `getCollection` returns the specified MongoCollection from the specified database using the current connection. 40 | 41 | ``` swift 42 | let collection = client.getCollection( 43 | databaseName: , 44 | collectionName: 45 | ) 46 | ``` 47 | 48 | #### Parameters 49 | 50 | * **databaseName:** String name of database to be used 51 | * **collectionName:** String name of collection to be retrieved 52 | 53 | ### Get Current Mongo Server Status 54 | 55 | `serverStatus` returns: a Result object representing the server status. 56 | 57 | ``` swift 58 | let status = client.serverStatus() 59 | ``` 60 | 61 | ### Return String Array of Current Database Names 62 | 63 | Use `databaseNames` to build a string array of current database names: 64 | 65 | 66 | ``` swift 67 | let dbnames = client.databaseNames() 68 | ``` 69 | 70 | -------------------------------------------------------------------------------- /guide/Hadoop.md: -------------------------------------------------------------------------------- 1 | # PerfectHadoop 2 | 3 | This project provides a set of Swift classes which enable access to Hadoop servers. 4 | 5 | This package builds with Swift Package Manager and is part of the [Perfect](https://github.com/PerfectlySoft/Perfect) project. It was written to be stand-alone and so does not require PerfectLib or any other components. 6 | 7 | Ensure you have installed and activated the latest Swift 4.0 tool chain. 8 | 9 | ## Release Note 10 | PerfectHadoop supports Hadoop 3.0.0 with a limitation on 2.7.3. 11 | 12 | ## Building 13 | Add this project as a dependency in your Package.swift file. 14 | 15 | ``` swift 16 | .Package(url:"https://github.com/PerfectlySoft/Perfect-Hadoop.git", majorVersion: 3) 17 | ``` 18 | 19 | Then please add the following line to the beginning part of swift sources: 20 | ``` swift 21 | import PerfectHadoop 22 | ``` 23 | 24 | ## Error Handle - `Exception` 25 | 26 | In case of operation failure, an exception might be thrown out. In most cases of Perfect-Hadoop, the library would probably throw a `Exception` object. User can catch it and check a tuple `(url, header, body)` of the failure, as demo below: 27 | 28 | ``` swift 29 | do { 30 | // some Perfect Hadoop operations, including WebHDFS / MapReduce / YARN, all of them: 31 | ... 32 | } 33 | catch(Exception.unexpectedResponse(let (url, header, body))) { 34 | print("Exception: \(url)\n\(header)\n\(body)") 35 | } 36 | catch (let err){ 37 | print("Other Error:\(err)") 38 | } 39 | ``` 40 | 41 | ## User Manual 42 | - WebHDFS: [Perfect-HDFS](HadoopWebHDFS.md) 43 | - MapReduce: 44 | * [Perfect-MapReduce Application Master API](HadoopMapReduceMaster.md) ⚠️ Experimental ⚠️ 45 | * [Perfect-MapReduce History Server API](HadoopMapReduceHistory.md) 46 | - YARN: 47 | * [Perfect-YARN Node Manager](HadoopYARNNodeManager.md) 48 | * [Perfect-YARN Resource Manager](HadoopYARNResourceManager.md) 49 | 50 | 51 | -------------------------------------------------------------------------------- /guide.zh_CN/staticFileContent.md: -------------------------------------------------------------------------------- 1 | # 静态文件 2 | 3 | 参考[请求响应路由](routing.md)一章,Perfect可以实现非常复杂的请求响应路由与定向,也因此当然可以很好地管理如HTML、图片、CSS样式表和JavaScript脚本这样的静态文件内容。 4 | 5 | 静态文件内容的调用是通过```StaticFileHandler```对象完成。一旦该对象实例收到请求,该对象会去检索目标文件,如果没有找到就返回404页面“文件不存在”。一个```StaticFileHandler```对象还可以通过使用ETag文件头的方法来缓冲处理非常大的文件。 6 | 7 | 当```StaticFileHandler```对象初始化时需要一个文档根目录作为参数。该根目录将成为其它所有静态文件的路径前缀。当前的HTTPRequest对象实例中的```path```路径属性将会用于显示目标文件的具体路径。 8 | 9 | ```StaticFileHandler```可以从您的网站句柄直接调用```handleRequest```方法而来 10 | 11 | 比如,句柄可以用于根据请求直接返回响应,如下所示: 12 | 13 | ``` swift 14 | { 15 | request, response in 16 | StaticFileHandler(documentRoot: request.documentRoot) 17 | .handleRequest(request: request, response: response) 18 | } 19 | ``` 20 | 21 | 如果您的服务器只存放静态网页,则可以直接从文档根目录启动: 22 | 23 | ``` swift 24 | try HTTPServer.launch(.server(name: "localhost", port: 8080, documentRoot: "/path/to/webroot")) 25 | ``` 26 | 27 | 以下示例展示了一个虚拟的文档路径,所有"/files"开头的URI都映射到了物理路径"/var/www/htdocs" 28 | 29 | 30 | ``` swift 31 | routes.add(method: .get, uri: "/files/**") { 32 | request, response in 33 | 34 | // 获得符合通配符的请求路径 35 | request.path = request.urlVariables[routeTrailingWildcardKey] 36 | 37 | // 用文档根目录初始化静态文件句柄 38 | let handler = StaticFileHandler(documentRoot: "/var/www/htdocs") 39 | 40 | // 用我们的根目录和路径 41 | // 修改集触发请求的句柄 42 | handler.handleRequest(request: request, response: response) 43 | ) 44 | ``` 45 | 46 | 在之前的请求响应路由例子中,一个发向“/files/foo.html”的请求将返回对应的文件“/var/www/htdocs/foo.html” 47 | 48 | ### 页面压缩和性能提升 49 | 50 | 默认情况下,StaticFileHandler 静态页面处理器会全力以赴把所有文件内容推送给客户端,也就是使用套接字的 `sendfile` 方法。不过有两种情况是无法实现的或者不希望发生的。 51 | 52 | 第一种情况是是用TLS/SSL 加密通信。原本文件处理器会把整个文件的打开并发出所有数据块,而一旦采取加密形式,文件处理器不会有额外操作,只不过检测到加密连接时会关闭 `sendfile`的使用。 53 | 54 | 另一种情况是流量压缩。内容压缩的过程不会和 `sendfile`进行混合使用。如果要启动压缩功能,只需要把`allowResponseFilters`打开即可: 55 | 56 | ```swift 57 | StaticFileHandler(documentRoot: "/path/to/root/", allowResponseFilters: true) 58 | ``` -------------------------------------------------------------------------------- /guide/formData.md: -------------------------------------------------------------------------------- 1 | # Using Form Data 2 | 3 | In a REST application, there are several common HTTP "verbs" that are used. The most common of these are the "GET" and "POST" verbs. 4 | 5 | > The best-practice assignment of when to use each verb can vary between methodologies and is beyond the scope of this documentation. 6 | 7 | An HTTP "GET" request only passes parameters in the URL: 8 | 9 | ``` 10 | http://www.example.com/page.html?message=Hello,%20World! 11 | ``` 12 | 13 | The "query parameters" in the above example are accessed using the `.queryParams` method: 14 | 15 | ``` swift 16 | let params = request.queryParams 17 | ``` 18 | 19 | While the above example only refers to a GET request, the `.queryParams` method applies to any HTTP request as they all can contain query parameters. 20 | 21 | ## POST Parameters 22 | 23 | POST parameters, or params, are the standard method for passing complex data between browsers and other sources to APIs for creating or modifying content. 24 | 25 |  Perfect’s HTTP libraries make it easy to access arrays of POST params or specific params. 26 | 27 | To return all params (Query or POST) as a `[(String,String)]` array: 28 | 29 | ``` swift 30 | let params = request.params() 31 | ``` 32 | 33 | To return only POST params as a `[(String,String)]` array: 34 | 35 | ``` swift 36 | let params = request.postParams() 37 | ``` 38 | 39 | 40 | To return all params with a specific name such as multiple checkboxes, type: 41 | 42 | ``` swift 43 | let params = request.postParams(name: ) 44 | ``` 45 | This returns an array of strings: `[String]` 46 | 47 | To return a specific parameter, as an optional `String?`: 48 | 49 | ``` swift 50 | let param = request.param(name: ) 51 | ``` 52 | 53 | When supplying a POST parameter in the `request` object is optional, it can be useful to specify a default value if one is not supplied. In this case, use the following syntax to return an optional `String?`: 54 | 55 | ``` swift 56 | let param = request.param(name: , defaultValue: ) 57 | ``` 58 | -------------------------------------------------------------------------------- /guide/GoogleAnalytics.md: -------------------------------------------------------------------------------- 1 | # Google Analytics Measurement Protocol 2 | 3 | The Google Analytics Measurement Protocol is the server-side equivalent of embedding Google Analytics into a web page. 4 | 5 | It means that you can log any sort of activity - Raw TCP or UDP events, or specific interactions that are triggered by events like AJAX. 6 | 7 | ## API Documentation 8 | 9 | For full API documentation, visit [https://www.perfect.org/docs/api-Perfect-GoogleAnalytics-MeasurementProtocol.html](https://www.perfect.org/docs/api-Perfect-GoogleAnalytics-MeasurementProtocol.html). 10 | 11 | The API documentation explains every property that can be set within the system. 12 | 13 | ## Configuration 14 | 15 | The `PerfectGAMeasurementProtocol` struct enables the setting of application-wide defaults for Property ID and Hit Type. 16 | 17 | ``` swift 18 | PerfectGAMeasurementProtocol.propertyid = "UA-XXXXXXXX-X" 19 | PerfectGAMeasurementProtocol.hitType = "pageview" 20 | ``` 21 | 22 | ## Building 23 | 24 | Add this project as a dependency in your Package.swift file. 25 | 26 | ``` swift 27 | .Package(url: "https://github.com/PerfectlySoft/Perfect-GoogleAnalytics-MeasurementProtocol.git", majorVersion: 3) 28 | ``` 29 | 30 | ## Example Usage 31 | 32 | To set up and execute the logging of an event: 33 | 34 | ```swift 35 | PerfectGAMeasurementProtocol.propertyid = "UA-XXXXXXXX-X" 36 | let gaex = PerfectGAEvent() 37 | gaex.user.uid = "donkey" 38 | gaex.user.cid = "kong" 39 | gaex.session.ua = "aua" 40 | gaex.traffic.ci = "ci" 41 | gaex.system.fl = "x" 42 | gaex.hit.ni = 2 43 | 44 | 45 | do { 46 | let str = try gaex.generate() 47 | print(str) 48 | let resp = gaex.makeRequest(useragent: "TestingAPI1.0", body: str) 49 | print(resp) 50 | } catch { 51 | print("\(error)") 52 | } 53 | 54 | ``` 55 | 56 | 57 | A series of common hit types and configurations can be found in Google's documentation, [https://developers.google.com/analytics/devguides/collection/protocol/v1/devguide#commonhits](https://developers.google.com/analytics/devguides/collection/protocol/v1/devguide#commonhits) 58 | 59 | -------------------------------------------------------------------------------- /guide.zh_CN/StORM-CouchDB.md: -------------------------------------------------------------------------------- 1 | # CouchDBStORM 数据库关系自动管理 2 | 3 | ### 在项目中引用 4 | 5 | 请在您当前工程的 Package.swift 中增加以下依存关系: 6 | 7 | ``` swift 8 | .Package(url: "https://github.com/SwiftORM/CouchDB-Storm.git", majorVersion: 3) 9 | ``` 10 | 11 | 12 | ## 连接到数据库 13 | 14 | 请设置如下属性以连接数据库: 15 | 16 | ``` swift 17 | CouchDBConnection.host = "localhost" 18 | CouchDBConnection.username = "username" 19 | CouchDBConnection.password = "secret" 20 | CouchDBConnection.port = 5984 21 | CouchDBConnection.ssl = true 22 | ``` 23 | 24 | 一旦设置信息确认,则数据连接会在对象应用过程中自动创建。 25 | 26 | ``` swift 27 | let obj = User() 28 | ``` 29 | 30 | ## CouchDBStORM 支持的函数方法 31 | 32 | ### 数据连接 33 | 34 | `CouchDBConnection` - 设置连接参数,用于访问 CouchDB 服务器。请确认连接所需的主机名、用户名和密码。如果采用非标准端口和加密传输SSL时,才需要设置端口和SSL。默认情况下SSL为false,即不使用SSL,默认端口为 5984。 35 | 36 | ### 创建数据库 37 | 38 | 对象所用的工作数据库是自动嵌入的,可以根据需要自行命名: 39 | 40 | ``` swift 41 | override open func database() -> String { 42 | return "我的数据库名字" 43 | } 44 | ``` 45 | 46 | `setup()` - 创建数据库 47 | 48 | > **⚠️注意⚠️** 其中,**主索引键** 是类对象内定义的第一个属性。 49 | 50 | #### 自定义表创建SQL语句 51 | 52 | `setup(String)` - 用于手工创建数据表,将会覆盖任何其他 StORM 设置语句。 53 | 54 | ### 保存数据 55 | 56 | `save(rev: String = "")` - 保存对象数据。如果定义了一个ID编号,则自动执行更新,否则将插入一个新的文档。rev参数用于说明文档修订版本号。如果为该参数为空的话,则会自动使用`_rev`属性值代替修订版本号。 57 | 58 | `save(rev: String = "") {id in ... }` - 保存对象数据。如果定义了一个ID编号,则自动执行更新,否则将插入一个新的文档。rev参数用于说明文档修订版本号。如果为该参数为空的话,则会自动使用`_rev`属性值代替修订版本号。闭包用于在新创建文档时返回新文档编号。 59 | 60 | `create()` - 显式要求创建新的数据库对象(而不是更新)。修订版本号也会在保存完毕后自动设置。 61 | 62 | ### 读取数据 63 | 64 | `get()` - 在假设id已经设置完成的条件下获取文档。 65 | 66 | `get(String)` - 根据id获取文档。 67 | 68 | `find([String: Any])` - 根据查询条件获取数据。比如,`try find(["用户名":"老张"])` 就能够过滤所有文档找到用户名等于“老张”的那个文档。关于查询选择条件,详见 [http://docs.couchdb.org/en/2.0.0/api/database/find.html#find-selectors](http://docs.couchdb.org/en/2.0.0/api/database/find.html#find-selectors) 69 | 70 | 此外,`find`还能包括: 71 | 72 | * `cursor: StORMCursor` - 可选的 `cursor` 游标对象是一个 [StORMCursor](https://github.com/PerfectlySoft/PerfectDocs/blob/master/guide.zh_CN/StORM-Cursor.md) 73 | 74 | ### 删除对象 75 | 76 | `delete()` - 删除当前对象,假设 id 编号和 _rev 修订号已经设置。 77 | 78 | 79 | -------------------------------------------------------------------------------- /guide/handlingRequests.md: -------------------------------------------------------------------------------- 1 | # Handling Requests 2 | As an internet server, Perfect's main function is to receive and respond to requests from clients. Perfect provides objects to represent the request and the response components, and it permits you to install handlers to control the resulting content generation. 3 | 4 | Everything begins with the creation of the server object. The server object is configured and subsequently binds and listens for connections on a particular port. When a connection occurs, the server begins reading the request data. Once the request has been completely read, the server will pass the [request object](HTTPRequest.md) through any request filters. 5 | 6 | These filters permit the incoming request to be modified. The server will then use the request's path URI and search the [routing](routing.md) system for an appropriate handler. If a handler is found, it is given a chance to populate a [response object](HTTPResponse.md). Once the handler indicates that it is done responding, the response object is passed through any response filters. These filters permit the outgoing data to be modified. The resulting data is then pushed to the client, and the connection is either closed, or it can be reused as an HTTP persistent connection, a.k.a. HTTP keep-alive, for additional requests and responses. 7 | 8 | Consult the following sections for more details on each specific phase and what can be accomplished during each: 9 | 10 | * [Routing](routing.md) - Describes the routing system and shows how to install URL handlers 11 | * [HTTPRequest](HTTPRequest.md) - Provides details on the request object protocol 12 | * [HTTPResponse](HTTPResponse.md) - Provides details on the response object protocol 13 | * [Request & Response Filters](filters.md) - Shows how to add filters and illustrates how they are useful 14 | 15 | In addition, the following sections show how to use some of the pre-made, specialized handlers to accomplish specific tasks: 16 | 17 | * [Static File Handler](staticFileContent.md) - Describes how to serve static file content 18 | * [Mustache](mustache.md) - Shows how to populate and serve Mustache template based content 19 | -------------------------------------------------------------------------------- /guide.zh_CN/StORM-MongoDB.md: -------------------------------------------------------------------------------- 1 | # MongoDBStORM 2 | 3 | ## 相关例子 4 | 5 | * [MongoDBStORM-Demo](https://github.com/PerfectExamples/MongoDBStORM-Demo) 6 | * [Perfect-Session-MongoDB-Demo](https://github.com/PerfectExamples/Perfect-Session-MongoDB-Demo) 7 | * [Perfect-Turnstile-MongoDB-Demo](https://github.com/PerfectExamples/Perfect-Turnstile-MongoDB-Demo) 8 | 9 | 10 | ## 使用方法 11 | 12 | 首先是在您的项目Package.swift中增加如下依存关系,注意该函数库包括了所有必要的其他组件,比如数据库连接件。 13 | 14 | ``` swift 15 | .Package(url: "https://github.com/SwiftORM/MongoDB-Storm.git", majorVersion: 3) 16 | ``` 17 | 18 | ## 连接到数据库服务器 19 | 20 | 连接到服务器需要设置信息参考范例如下: 21 | 22 | ``` swift 23 | MongoDBConnection.host = "localhost" 24 | MongoDBConnection.port = 27017 25 | MongoDBConnection.ssl = true 26 | MongoDBConnection.database = "mydb" 27 | 28 | // 用户名密码 29 | // 只有在选项 authModeType = .standard 时才用得上 30 | MongoDBConnection.username = "username" 31 | MongoDBConnection.password = "secret" 32 | MongoDBConnection.authdb = "authenticationSource" 33 | 34 | // 身份认证模式: 35 | // .none (默认), 或 .standard 36 | MongoDBConnection.authModeType = .none 37 | ``` 38 | 39 | 输入完上述信息后,将在第一次使用对象类时自动激活连接。 40 | 41 | ``` swift 42 | let obj = User() 43 | ``` 44 | 45 | ## MongoDBStORM 支持的方法 46 | 47 | ### 数据库连接 48 | 49 | `MongoDBConnection` - 设置芒果数据库的连接参数,包括主机名、数据库信息、端口、加密证书SSL等等(默认不使用SSL,端口号是27017)。 50 | 51 | ### 创建集合 52 | 53 | 创建集合的请参考以下初始化函数,类对象(集合)名称可以自行决定: 54 | 55 | ``` swift 56 | override init() { 57 | super.init() 58 | _collection = "users_demo" 59 | } 60 | ``` 61 | 62 | 注意与其他数据库版本的StORM关系管理自动化方法不同,芒果数据库不需要使用`setup()`函数用于创建集合。MongoDB会根据需要在数据插入时自动创建。 63 | 64 | > **注意** 在类对象中 **主索引** 是第一条属性。 65 | 66 | ### 保存数据 67 | 68 | `save()` - 保存对象的当前数据值。如果ID编号指定,则采用覆盖更新的方法保存;否则将创建新的数据记录。 69 | 70 | ### 获取数据 71 | 72 | `get()` - 获取记录,此时假定ID编号已经提前设置好。 73 | 74 | `get(String)` - 根据具体编号获取记录/ 75 | 76 | `find([String: Any])` - 执行查询。比如、 `try find(["username":"joe"])` 会检索所有用户名为"joe"的记录。 77 | 78 | 此外,`find` 还包括: 79 | 80 | * `cursor: StORMCursor` - 可选的游标`cursor`对象,参考 [StORMCursor](https://github.com/PerfectlySoft/PerfectDocs/blob/master/guide.zh_CN/StORM-Cursor.md) 81 | 82 | ### 删除数据 83 | 84 | `delete()` - 删除当前数据。假设该对象id编号提前准备好。 85 | 86 | 87 | -------------------------------------------------------------------------------- /guide.zh_CN/StORM.md: -------------------------------------------------------------------------------- 1 | # Swift 对象关系管理:“StORM”函数库 2 | 3 | StORM 是为 Swift 语言配套的对象管理函数库(ORM),基于[Perfect](https://github.com/PerfectlySoft/Perfect) 软件架构。 4 | 5 | 该函数库的设计方向瞄准了易学易用、配置灵活,并为程序员提供程序内的数据结构与数据库内的数据结构的一致性。 6 | 7 | ## StORM 文档内容 8 | [类对象设置](https://github.com/PerfectlySoft/PerfectDocs/blob/master/guide.zh_CN/StORM-Setting-up-a-class.md) 如何从StORM创建一个类对象并实现绑定数据表格。 9 | 10 | [数据记录增删改](https://github.com/PerfectlySoft/PerfectDocs/blob/master/guide.zh_CN/StORM-Saving-Retrieving-and-Deleting-Rows.md) 基本数据库操作 11 | 12 | [数据记录游标](https://github.com/PerfectlySoft/PerfectDocs/blob/master/guide.zh_CN/StORM-Cursor.md) 管理查询结果分页。 13 | 14 | [插入数据行](https://github.com/PerfectlySoft/PerfectDocs/blob/master/guide.zh_CN/StORM-Insert.md) 关于插入数据行的更多细节。 15 | 16 | [数据行更新](https://github.com/PerfectlySoft/PerfectDocs/blob/master/guide.zh_CN/StORM-Update.md) 关于数据行修改的更多细节。 17 | 18 | ## StORM 有关的数据库(数据源)文档: 19 | 20 | * [SQLite3](https://github.com/PerfectlySoft/PerfectDocs/blob/master/guide.zh_CN/StORM-SQLite.md) 21 | * [PostgreSQL](https://github.com/PerfectlySoft/PerfectDocs/blob/master/guide.zh_CN/StORM-PostgreSQL.md) 22 | * [MySQL](https://github.com/PerfectlySoft/PerfectDocs/blob/master/guide.zh_CN/StORM-MySQL.md) 23 | * * [Apache CouchDB](https://github.com/PerfectlySoft/PerfectDocs/blob/master/guide.zh_CN/StORM-CouchDB.md) 24 | 25 | 26 | ### 在您的项目中使用本函数库 27 | 28 | 请根据项目需要选择适合您的数据源,并根据数据源的类型确定在您项目中的 Package.swift 中设置必要的依存关系。 29 | 30 | 比如,如果需要使用PostgreSQL,则请配置为 PostgresStORM 程序库: 31 | 32 | ``` swift 33 | .Package(url: "https://github.com/SwiftORM/Postgres-StORM.git", majorVersion: 3) 34 | ``` 35 | 36 | 比如,如果需要使用 MySQL,则请配置为 MySQLStORM 程序库: 37 | 38 | ``` swift 39 | .Package(url: "https://github.com/SwiftORM/MySQL-StORM.git", majorVersion: 3) 40 | ``` 41 | 42 | 比如,如果需要使用 SQLite,则请配置为 SQLiteStORM 程序库: 43 | 44 | ``` swift 45 | .Package(url: "https://github.com/SwiftORM/SQLite-StORM.git", majorVersion: 3) 46 | ``` 47 | 48 | 比如,如果要使用 CouchDB,则请配置为 CouchDBStORM 程序库: 49 | 50 | ``` swift 51 | .Package(url: "https://github.com/SwiftORM/CouchDB-StORM.git", majorVersion: 3) 52 | ``` 53 | 54 | 如果您在使用 Xcode,请务必在改变 `Package.swift` 文件之后,需要再次运行 SPM 管理脚本重建 Xcode 项目: 55 | 56 | ``` 57 | swift package generate-xcodeproj 58 | ``` 59 | -------------------------------------------------------------------------------- /guide.zh_CN/logFiles.md: -------------------------------------------------------------------------------- 1 | # log日志文件 2 | 3 | `PerfectLogger` 模块可以用于实现将事件记录到特定文件中去。 4 | 5 | ## 使用方法 6 | 7 | 请修改您的项目文件 Package.swift 并增加以下内容: 8 | 9 | ``` swift 10 | .Package(url: "https://github.com/PerfectlySoft/Perfect-Logger.git", majorVersion: 3), 11 | ``` 12 | 13 | 然后您可以用 `import` 语句导入该函数库: 14 | 15 | ``` swift 16 | import PerfectLogger 17 | ``` 18 | 19 | 参考下面的示范实现同时在终端控制台和指定文件中输出事件描述: 20 | 21 | ``` swift 22 | LogFile.debug("调试信息", logFile: "test.txt") 23 | LogFile.info("综合消息", logFile: "test.txt") 24 | LogFile.warning("警告信息", logFile: "test.txt") 25 | LogFile.error("错误信息", logFile: "test.txt") 26 | LogFile.critical("严重警告", logFile: "test.txt") 27 | LogFile.terminal("服务器终止", logFile: "test.txt") 28 | ``` 29 | 30 | 如果要将日志写入默认文件,则可以忽略第二个参数,也就是日志文件名参数。 31 | 32 | ## 在事件之间通过设置"eventid"进行关联 33 | 34 | 每个日志记录的事件都能返回一个事件的id标识符。将事件id标识符应用到新的事件上最直接的好处就是可以实现各个事件之间的关联关系,比如: 35 | 36 | ``` swift 37 | let eid = LogFile.warning("test 1") 38 | LogFile.critical("test 2", eventid: eid) 39 | ``` 40 | 41 | 则在日志中的记录会变成: 42 | 43 | ``` 44 | [WARNING] [62f940aa-f204-43ed-9934-166896eda21c] [2016-11-16 15:18:02 GMT-05:00] test 1 45 | [CRITICAL] [62f940aa-f204-43ed-9934-166896eda21c] [2016-11-16 15:18:02 GMT-05:00] test 2 46 | ``` 47 | 48 | 返回的结果被标记为可丢弃结果 `@discardableResult` (swift 编译标志),因此如果没有重复使用的需要时,可以安全地忽略。 49 | 50 | 51 | ## 定制日志文件位置 52 | 53 | 默认的日志文件名为`./log.log`。如果您希望另外选择一个位置用于存储日志文件,请设置变量 `LogFile.location`: 54 | 55 | ``` swift 56 | LogFile.location = "/var/log/myLog.log" 57 | ``` 58 | 这样上述消息就可以直接写入文件了: 59 | 60 | ``` swift 61 | LogFile.debug("调试") 62 | LogFile.info("消息") 63 | LogFile.warning("警告") 64 | LogFile.error("出错") 65 | LogFile.critical("严重错误") 66 | LogFile.terminal("服务器终止") 67 | ``` 68 | 69 | ## 输出效果 70 | 71 | ``` 72 | [DEBUG] [ec6a9ca5-00b1-4656-9e4c-ddecae8dde02] [2016-11-16 15:18:02 GMT-05:00] 调试 73 | [INFO] [ec6a9ca5-00b1-4656-9e4c-ddecae8dde02] [2016-11-16 15:18:02 GMT-05:00] 消息 74 | [WARNING] [ec6a9ca5-00b1-4656-9e4c-ddecae8dde02] [2016-11-16 15:18:02 GMT-05:00] 警告 75 | [ERROR] [62f940aa-f204-43ed-9934-166896eda21c] [2016-11-16 15:18:02 GMT-05:00] 出错 76 | [CRITICAL] [62f940aa-f204-43ed-9934-166896eda21c] [2016-11-16 15:18:02 GMT-05:00] 严重错误 77 | [EMERG] [ec6a9ca5-00b1-4656-9e4c-ddecae8dde02] [2016-11-16 15:18:02 GMT-05:00] 服务器终止 78 | ``` 79 | -------------------------------------------------------------------------------- /guide.zh_CN/deployment-Ubuntu.md: -------------------------------------------------------------------------------- 1 | # 为Swift 4 + Perfect 3 创建 Ubuntu 基本镜像 2 | 3 | 本文将帮助您创建Ubuntu 镜像以使用Swift 4语言在PerfectlySoft公司的Perfect 3应用程序框架下开发项目。 4 | 5 | ## Running as the Root User 6 | 7 | 请首先安装准备好一个Ubuntu系统。为了方便学习,请按照以 **root** 用户顺序执行命令。您需要使用管理员账号和配套的密码。如果忽略这一步,那么在以下大部分内容内都需要使用 **sudo** 命令作为所有命令的开头 8 | 9 | ``` 10 | sudo su 11 | ``` 12 | 13 | ## Running a Screen Session 14 | 15 | 如果您是用 **SSH** 连接Ubuntu的,那么您可能需要为以下步骤创建一个 **screen** 会话过程。这种方式能够保证系统在即使您连接到服务器的会话中断的情况下依然不会挂起。这一步不是必须的,可以跳过去看下一步。如果需要建立一个名为“perfect”的 **screen** 会话,请使用: 16 | 17 | ``` 18 | screen -S perfect 19 | ``` 20 | 21 | 如果 **screen** 没有安装,请用下面的命令行安装: 22 | 23 | ``` 24 | apt-get install screen 25 | ``` 26 | 27 | 如果需要断开 **screen** 会话连接,可以使用组合键 **Control-A, D** 。如果需要重新回到被断开的 **screen** 会话,请使用命令: 28 | 29 | ``` 30 | screen -r perfect 31 | ``` 32 | 33 | 如果是不小心断开的 **screen** 会话,可以使用以下命令重新连接: 34 | 35 | ``` 36 | screen -D -R perfect 37 | ``` 38 | 39 | ## 通过APT安装项目必要的依存关系库 40 | 41 | Swift 4 + Perfect 3需要一些函数库以支持其运行,可以通过一个开源的脚本进行安装:[Perfect Ubuntu](https://github.com/PerfectlySoft/Perfect-Ubuntu.git) 42 | 43 | ## 从源代码安装 MongoDB 44 | 45 | 用下面的方法可以下载 MongoDB 源代码: 46 | 47 | ``` 48 | cd /usr/src/ 49 | wget https://github.com/mongodb/mongo-c-driver/releases/download/1.3.5/mongo-c-driver-1.3.5.tar.gz 50 | ``` 51 | 52 | 下载后请解压缩: 53 | 54 | ``` 55 | gunzip mongo-c-driver-1.3.5.tar.gz 56 | ``` 57 | 58 | 然后展开 MongoDB 源代码: 59 | 60 | ``` 61 | tar -xvf mongo-c-driver-1.3.5.tar 62 | ``` 63 | 64 | 再删除 MongoDB 源代码档案包: 65 | 66 | ``` 67 | rm mongo-c-driver-1.3.5.tar 68 | ``` 69 | 70 | 在编译 MongoDB 源代码前首先执行配置命令: 71 | 72 | ``` 73 | cd mongo-c-driver-1.3.5/ 74 | ./configure --enable-sasl=yes 75 | ``` 76 | 77 | 编译 MongoDB: 78 | 79 | ``` 80 | make 81 | ``` 82 | 83 | 安装 MongoDB: 84 | 85 | ``` 86 | make install 87 | ``` 88 | 89 | ## 结束 90 | 91 | 恭喜!现在您的系统环境已经可以支持Swift 4 + Perfect 3联合工作。 92 | 93 | ## 尝鲜 94 | 95 | 获取 Perfect 3 模板项目: 96 | 97 | ``` 98 | git clone https://github.com/PerfectlySoft/PerfectTemplate 99 | ``` 100 | 101 | 并编译运行: 102 | 103 | ``` 104 | cd PerfectTemplate 105 | swift run 106 | ``` 107 | 108 | 下一步即可通过 `curl` 命令测试服务器: 109 | 110 | ``` 111 | curl http://127.0.0.1:8181 112 | ``` 113 | 114 | 如果您发现任何问题,请进行[问题报告](http://jira.perfect.org:8080/servicedesk/customer/portal/1). 115 | -------------------------------------------------------------------------------- /guide.zh_CN/csrf.md: -------------------------------------------------------------------------------- 1 | ## CSRF (跨网站请求伪造) 安全功能 2 | 3 | 跨网站请求伪造是一种网络攻击方法,能够在用户不知情的情况下代理用户已经完成的身份验证在目标网站上执行非法操作。 **CSRF 攻击主要瞄准状态变更操作,而不是盗取数据,因为攻击者无法看到来源请求的响应结果。** 此类攻击一般利用社交媒体设下陷阱(比如通过邮件或者聊天发送链接),通过引诱用户点击链接执行攻击者的操作。典型的受害者可能在不知情的情况下被转移资金,或者改变了邮件地址等等。如果受害者是一个具有高度权限的账户,比如管理员账户,则整个网站都可能被劫持。[1] - (OWASP) 4 | 5 | CSRF 是一种经常被忽视的攻击方法,因此常常造成混乱,除非在整个软件体系上加以防范。一旦在最高级别的体系上实施防范,则网站整体安全性会得到大大提高。 6 | 7 | [Perfect Sessions](https://github.com/PerfectlySoft/PerfectDocs/blob/master/guide/sessions.md) 会话模块包括了对 CSRF 的配置方法。 8 | 9 | 如果您的工程软件配置文件 Package.Swift 包括了 Perfect Sessions 会话管理,或者任何带有数据源驱动的应用实现,则您的工程已经包括CSRF支持。 10 | 11 | ## 相关范例 12 | 13 | * [Perfect-Session-Memory-Demo](https://github.com/PerfectExamples/Perfect-Session-Memory-Demo) 14 | 15 | 16 | ## 配置 17 | 18 | 一个典型的 CSRF 配置可能看起来像这样 19 | 20 | ``` swift 21 | SessionConfig.CSRF.checkState = true 22 | SessionConfig.CSRF.failAction = .fail 23 | SessionConfig.CSRF.checkHeaders = true 24 | SessionConfig.CSRF.acceptableHostnames.append("http://www.example.com") 25 | SessionConfig.CSRF.requireToken = true 26 | ``` 27 | 28 | ### SessionConfig.CSRF.checkState 29 | 30 | 这是总开关,如果开启,则CSRF将在所有路由上启动安全管制。 31 | 32 | ### SessionConfig.CSRF.failAction 33 | 34 | 该选项用于处理 CSRF 认证失败时应该采取的操作,允许值为: 35 | 36 | * `.fail` - 执行暂停。服务器将停止处理任何新请求操作,而HTTP状态会变更为 `406 Not Acceptable`。 37 | * `.log` - 允许继续服务,但是事件将记录到日志中去。 38 | * `.none` - 允许继续服务,而且也不会采取任何附加操作。 39 | 40 | ### SessionConfig.CSRF.acceptableHostnames 41 | 42 | 该数组用于代表允许进行CSRF的主机名称。 43 | 44 | 45 | ### SessionConfig.CSRF.checkHeaders 46 | 47 | 如果 `CORS.checkheader` 被启动(配置为 `true`)请求来源和主机名将被验证。 48 | 49 | * 请求头数据必须包括 `Origin` 和 `Referrer` 或 `X-Forwarded-For` 三个内容段。 50 | * 如果 "origin" 值已经在配置选项 `SessionConfig.CSRF.acceptableHostnames`名单中,则 CSRF 检查则认可该来源,结束认证并执行请求后续操作。 51 | * 请求必须包括 `Host` 或 `X-Forwarded-Host`,用于代表代理主机 ("host"). 52 | * "host" 和 "origin" 值必须完全匹配。 53 | 54 | 55 | 56 | ### SessionConfig.CSRF.requireToken 57 | 58 | 当设置为真时,该设置会要求所有 HTTP POST 包括一个 "_csrf" 参数,或者如果内容类型为"application/json"时请求头数据必须包括一个相关的 "X-CSRF-Token" 票据变量。请求的内容或参数必须和`request.session.data["csrf"]`数值匹配。该数值在会话开始时自动设置。 59 | 60 | ### 会话状态 61 | 62 | 虽然不是一个配置选项,但是需要注意的时,如果总开关`SessionConfig.CSRF.checkState`设置为真,则如果会话是一个“新会话”,则不会允许任何POST请求。这是根据安全建议中特别设置的特征。 63 | 64 | 65 | 66 | 67 | 68 | [1] - OWASP, Cross-Site Request Forgery (CSRF): [https://www.owasp.org/index.php/Cross-Site\_Request\_Forgery\_(CSRF)](https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)) -------------------------------------------------------------------------------- /guide/webRedirects.md: -------------------------------------------------------------------------------- 1 | # Web Redirects 2 | 3 | The Perfect WebRedirects module will filter for specified routes (including trailing wildcard routes) and perform redirects as instructed if a match is found. 4 | 5 | This can be important for maintaining SEO ranking in systems that have moved. For example, if moving from a static HTML site where `/about.html` is replaced with the new route `/about` and no valid redirect is in place, the site or system will lose SEO ranking. 6 | 7 | A demo showing the usage, and working of the Perfect WebRedirects module can be found at [Perfect-WebRedirects-Demo](https://github.com/PerfectExamples/Perfect-WebRedirects-Demo). 8 | 9 | ## Including in your project 10 | 11 | Import the dependency into your project by specifying it in your project's Package.swift file, or adding it via Perfect Assistant. 12 | 13 | ``` swift 14 | .Package(url: "https://github.com/PerfectlySoft/Perfect-WebRedirects", majorVersion: 3), 15 | ``` 16 | 17 | Then in your `main.swift` file where you configure your web server, add it as an import, and add the filter: 18 | 19 | ``` swift 20 | import PerfectWebRedirects 21 | ``` 22 | 23 | Adding the filter: 24 | 25 | ``` swift 26 | // Add to the "filters" section of the config: 27 | [ 28 | "type":"request", 29 | "priority":"high", 30 | "name":WebRedirectsFilter.filterAPIRequest, 31 | ] 32 | ``` 33 | 34 | If you are also adding Request Logger filters, if the Web Redirects object is added second, directly after the RequestLogger filter, then both the original request (and its associated redirect code) and the new request will be logged correctly. 35 | 36 | ## Configuration file 37 | 38 | The configuration for the routes is included in JSON files at `/config/redirect-rules/*.json` in the form: 39 | 40 | ``` 41 | { 42 | 43 | "/test/no": { 44 | "code": 302, 45 | "destination": "/test/yes" 46 | }, 47 | 48 | "/test/no301": { 49 | "code": 301, 50 | "destination": "/test/yes" 51 | }, 52 | 53 | "/test/wild/*": { 54 | "code": 302, 55 | "destination": "/test/wildyes" 56 | }, 57 | 58 | "/test/wilder/*": { 59 | "code": 302, 60 | "destination": "/test/wilding/*" 61 | } 62 | 63 | } 64 | ``` 65 | 66 | Note that multiple JSON files can exist in this directory; all will be loaded the first time the filter is invoked. 67 | 68 | The "key" is the matching route (the "old" file or route), and the "value" contains the HTTP code and new destination route to redirect to. -------------------------------------------------------------------------------- /guide.zh_CN/fileUploads.md: -------------------------------------------------------------------------------- 1 | # 文件上传 2 | 3 | 这里有一个[数据上传](formData.md)的例子,用于说明如何操作文件上载。 4 | 5 | HTTP表单数据主要采用以下两种编码格式: 6 | 7 | * application/x-www-form-urlencoded (默认编码格式) 8 | * multipart/form-data 9 | 10 | 如果要使用文件上传空间,则必须选择`multipart/form-data`作为表单的`enctype`编码类型。 11 | 12 | 完整的例子请查看[Perfect文件上传例子](https://github.com/iamjono/perfect-file-uploads) 13 | 14 | 典型的HTML文件上传表单中编码类型声明应该像以下例子这样: 15 | 16 | ``` 17 |
21 | 22 |
23 | 24 |
25 | ``` 26 | 27 | ## 在服务器端接收文件 28 | 29 | 因为表单是POST方法,我们需要用`.post`方法管理请求响应路由 30 | 31 | ``` swift 32 | var routes = Routes() 33 | routes.add( 34 | method: .post, 35 | uri: "/upload", 36 | handler: handler) 37 | server.addRoutes(routes) 38 | ``` 39 | 40 | 一旦对方请求内容完成文件传输,则我们可以使用请求句柄`handler`: 41 | 42 | ``` swift 43 | // 通过操作fileUploads数组来掌握文件上传的情况 44 | // 如果这个POST请求不是分段multi-part类型,则该数组内容为空 45 | 46 | if let uploads = request.postFileUploads where uploads.count > 0 { 47 | // 创建一个字典数组用于检查已经上载的内容 48 | var ary = [[String:Any]]() 49 | 50 | for upload in uploads { 51 | ary.append([ 52 | "fieldName": upload.fieldName, //字段名 53 | "contentType": upload.contentType, //文件内容类型 54 | "fileName": upload.fileName, //文件名 55 | "fileSize": upload.fileSize, //文件尺寸 56 | "tmpFileName": upload.tmpFileName //上载后的临时文件名 57 | ]) 58 | } 59 | values["files"] = ary 60 | values["count"] = ary.count 61 | } 62 | ``` 63 | 64 | 如上所述,被上传的文件(一个或多个文件)可以用`request.postFileUploads`数组表示,每个数组元素都有不同的属性,如`fileName`文件名、`fileSize`文件尺寸、`tmpFileName`临时文件名等等。 65 | 66 | **⚠️注意⚠️** :文件上传后会被自动放置到一个临时目录。您需要自己将临时文件转移至期望位置。 67 | 68 | 因此我们可以创建一个目录来放置这些上传来的文件。该目录因安全考虑不会和webroot的根目录放在一起: 69 | 70 | ``` swift 71 | // 创建路径用于存储已上传文件 72 | let fileDir = Dir(Dir.workingDir.path + "files") 73 | do { 74 | try fileDir.create() 75 | } catch { 76 | print(error) 77 | } 78 | ``` 79 | 80 | 下一步,在`for upload in uploads`代码段,我们会执行文件转移的操作: 81 | 82 | ``` swift 83 | // 将文件转移走,如果目标位置已经有同名文件则进行覆盖操作。 84 | let thisFile = File(upload.tmpFileName) 85 | do { 86 | let _ = try thisFile.moveTo(path: fileDir.path + upload.fileName, overWrite: true) 87 | } catch { 88 | print(error) 89 | } 90 | ``` 91 | 92 | 现在上传完毕的文件就可以按照原来的文件名转移到目标目录。 93 | 94 | 关于文件系统操作的详细内容,请参考[文件操作](dir.md) 和[目录操作](file.md)章节。 95 | -------------------------------------------------------------------------------- /guide.zh_CN/Redis.md: -------------------------------------------------------------------------------- 1 | # Redis 2 | 3 | Redis是BSD许可的开源、内存驻留存储的数据结构仓库,经常用于数据库、高速缓冲和消息代理。 4 | 5 | 详见[http://redis.io](http://redis.io/) 6 | 7 | Redis 数据库连接器 - Perfect 软件框架 8 | 9 | 10 | ## 快速上手 11 | 12 | 通过默认参数获得Redis连接: 13 | 14 | ```swift 15 | RedisClient.getClient(withIdentifier: RedisClientIdentifier()) { 16 | c in 17 | do { 18 | let client = try c() 19 | ... 20 | } catch { 21 | ... 22 | } 23 | } 24 | ``` 25 | 26 | 测试数据库连接效果: 27 | 28 | ```swift 29 | client.ping { 30 | response in 31 | defer { 32 | RedisClient.releaseClient(client) 33 | } 34 | guard case .simpleString(let s) = response else { 35 | ... 36 | return 37 | } 38 | XCTAssert(s == "PONG", "响应无效: \(response)") 39 | } 40 | ``` 41 | 42 | 设置变量和值 43 | 44 | ```swift 45 | let (key, value) = ("mykey", "myvalue") 46 | client.set(key: key, value: .string(value)) { 47 | response in 48 | guard case .simpleString(let s) = response else { 49 | ... 50 | return 51 | } 52 | client.get(key: key) { 53 | response in 54 | defer { 55 | RedisClient.releaseClient(client) 56 | } 57 | guard case .bulkString = response else { 58 | ... 59 | return 60 | } 61 | let s = response.toString() 62 | XCTAssert(s == value, "响应无效: \(response)") 63 | } 64 | } 65 | ``` 66 | 67 | 发布/订阅: 68 | 69 | ```swift 70 | RedisClient.getClient(withIdentifier: RedisClientIdentifier()) { 71 | c in 72 | do { 73 | let client1 = try c() 74 | RedisClient.getClient(withIdentifier: RedisClientIdentifier()) { 75 | c in 76 | do { 77 | let client2 = try c() 78 | client1.subscribe(channels: ["foo"]) { 79 | response in 80 | client2.publish(channel: "foo", message: .string("Hello!")) { 81 | response in 82 | client1.readPublished(timeoutSeconds: 5.0) { 83 | response in 84 | guard case .array(let array) = response else { 85 | ... 86 | return 87 | } 88 | XCTAssert(array.count == 3, "Invalid array elements") 89 | XCTAssert(array[0].toString() == "message") 90 | XCTAssert(array[1].toString() == "foo") 91 | XCTAssert(array[2].toString() == "Hello!") 92 | } 93 | } 94 | } 95 | } catch { 96 | ... 97 | } 98 | } 99 | } catch { 100 | ... 101 | } 102 | } 103 | ``` 104 | 105 | ## 编译 106 | 107 | 请在Package.swift 文件中增加依存关系: 108 | 109 | ``` 110 | .Package(url: "https://github.com/PerfectlySoft/Perfect-Redis.git", majorVersion: 3) 111 | ``` 112 | 113 | 114 | -------------------------------------------------------------------------------- /guide.zh_CN/deployment-Docker.md: -------------------------------------------------------------------------------- 1 | # Docker Deployment 2 | 3 | 本文介绍了如何为您的Swift + Perfect应用程序部署Docker镜像,特别是有代表性的 *perfect2-swift20160620-ubuntu1510* 镜像,内容包含: 4 | 5 | * Ubuntu 15.10 6 | * Swift 开发版本 016-06-20 (在Swift 4发行前的版本) 7 | * Perfect 3 8 | 9 | Docker 在您的系统下可以按照如下方式进行安装: 10 | 11 | * 如果您使用的是Ubuntu系统,则请用下面的命令行进行安装: 12 | `apt-get install docker` 13 | 14 | * 如果您使用的是macOS系统,安装方法详见: 15 | [Docker在mac上的安装方法](https://docs.docker.com/engine/installation/mac/) 16 | 17 | * 如果您使用的是微软的Windows系统,详细安装方法请见: 18 | [Docker的Windows安装指南](https://docs.docker.com/engine/installation/windows/) 19 | 20 | * 对于其它Linux系统,详细安装方法请见: 21 | [Docker在Linux系统上的安装指南](https://docs.docker.com/engine/installation/) 22 | 23 | 一旦Docker安装完成,您就可以用下列命令获取 perfect2-swift20160620-ubuntu1510 资源文件: 24 | 25 | ``` 26 | docker pull perfectlysoft/perfect2-swift20160620-ubuntu1510 27 | ``` 28 | 29 | 显现可以从Docker镜像上继续开发新程序了。为了方便演示,我们假定您的项目放在GitHub上: 30 | 31 | ``` 32 | https://github.com/PerfectlySoft/PerfectTemplate 33 | ``` 34 | 35 | 现在需要从Docker容器中启动终端控制台: 36 | 37 | ``` 38 | docker run -t -i perfectlysoft/perfect2-swift20160620-ubuntu1510 bash 39 | ``` 40 | 41 | 现在终端控制台应该已经在Docker容器内开始运行。请进入 `/usr/src/` 目录: 42 | 43 | ``` 44 | cd /usr/src/ 45 | ``` 46 | 47 | 将您的应用程序从Git代码资源库中克隆: 48 | 49 | ``` 50 | git clone https://github.com/PerfectlySoft/PerfectTemplate 51 | ``` 52 | 53 | 进入您的项目文件夹: 54 | 55 | ``` 56 | cd PerfectTemplate/ 57 | ``` 58 | 59 | 请注意Docker容器需要联网运行。用下面的命令行进行项目编译: 60 | 61 | ``` 62 | swift build 63 | ``` 64 | 65 | 编译完成后,您的应用程序应该出现在 `.build/debug/` 目录下。现在可以把这个文件拷贝到任何需要的地方,比如 `/root/` 目录: 66 | 67 | ``` 68 | cp .build/debug/PerfectTemplate /root/ 69 | ``` 70 | 71 | 现在请检查您的容器主机名: 72 | 73 | ``` 74 | hostname 75 | ``` 76 | 77 | 比如,主机名可能是 `2babae7df6fe`。随后退出容器: 78 | 79 | ``` 80 | exit 81 | ``` 82 | 83 | 现在您的新Swift + Perfect项目程序镜像已经准备完毕,请使用对应的主机名推送到云端: 84 | 85 | ``` 86 | docker commit -m "My application" -a "My Name" 2babae7df6fe myapp 87 | ``` 88 | 89 | (您可以为项目应用程序自定名称,取代“My Application”,同时用您本人的名字、公司的名字代替“My Name”,并用一个Docker镜像名取代“myapp”) 90 | 91 | 现在可以确认您的镜像已经创建: 92 | 93 | ``` 94 | docker images 95 | ``` 96 | 97 | 现在可以将您的应用程序部署为: 98 | 99 | ``` 100 | docker run -d -p 0.0.0.0:8080:8181 myapp /root/PerfectTemplate 101 | ``` 102 | 103 | 上面的命令意思是将您本地主机(`0.0.0.0`)的TCP端口`8080` 重新定向到您的Docker容器`8181`端口(基于您自己的应用镜像)并运行您的应用程序(`/root/PerfectTemplate`)。在本例子中,PerfectTemplate模板程序监听TCP端口`8181`。您可以用下面的命令行进行测试: 104 | 105 | ``` 106 | curl http://127.0.0.1:8080 107 | ``` 108 | -------------------------------------------------------------------------------- /guide.zh_CN/cors.md: -------------------------------------------------------------------------------- 1 | ## CORS (跨来源资源共享)安全功能 2 | 3 | 跨来源资源共享 (CORS) 是开放互联网的重要组成部分。换言之,没有CORS的支持,任何互联网框架都不会是完整的。 4 | 5 | 参考蒙梭·侯赛因的[html5rocks.com introduces CORS very effectively](https://www.html5rocks.com/en/tutorials/cors/)教学内容: 6 | 7 | > 互联网服务接口API就好像编织这张大网用的丝和线一样,但是使用过程中资源跨域传输可能会有些困难,特别是这些跨域请求受限于如 JSON-P 或者设置代理服务器等技术 —— JSON-P有安全隐患,而代理服务器的设置和维护会比较复杂。 8 | > 9 | > 跨来源资源共享(CORS)是一个W3C标准,允许从浏览器进行跨域访问,在XMLHttpRequest基础上实现。该标准允许程序员处理资源时,采用与同区域请求相同的方式。 10 | > 11 | > CORS的使用方法很简单。假设网站“张三.com”有一些数据需要“李四.com”获取,那么传统方式下受限于浏览器的“会话目标网站的唯一性限制”,这种方式是不可能被允许的。但是,如果有了CORS的支持,“张三.com”可以增加一些特殊的响应头数据,以允许“李四.com”进行访问。 12 | > 13 | > 如本示范内容所示,CORS支持需要服务器和客户端之间协同。如果您是前端工程师则不需要理会这些细节。本文剩下的内容是展示客户机如何实现跨来源请求,并且服务器如何进行配置以支持CORS。 14 | 15 | [Perfect 会话](https://github.com/PerfectlySoft/PerfectDocs/blob/master/guide/sessions.md) 模块包括了CORS配置支持,使得您的服务函数接口API可以根据需要允许或者禁止此类资源共享。 16 | 17 | 如果您的工程软件配置文件 Package.Swift 包括了 Perfect Sessions 会话管理,或者任何带有数据源驱动的应用实现,则您的工程已经包括CORS支持;但是请注意,默认情况下该支持处于 **关闭** 状态。 18 | 19 | ## 相关例子 20 | 21 | * [Perfect-Session-Memory-Demo](https://github.com/PerfectExamples/Perfect-Session-Memory-Demo) 22 | 23 | ## 配置 24 | 25 | ``` swift 26 | // 总开关;默认为关闭。 27 | SessionConfig.CORS.enabled = true 28 | 29 | // 允许进行跨来源资源共享的主机清单。 30 | // 如果希望不限制任何主机访问,则只需要保留一个通配符元素“*”即可。 31 | SessionConfig.CORS.acceptableHostnames = ["*"] 32 | 33 | // 否则如果需要追加特定域名 34 | SessionConfig.CORS.acceptableHostnames.append("http://www.test-cors.org") 35 | 36 | // 在域名中的开始和结束可以使用通配符 37 | SessionConfig.CORS.acceptableHostnames.append("*.example.com") 38 | SessionConfig.CORS.acceptableHostnames.append("http://www.domain.*") 39 | 40 | // 允许使用的方法列表 41 | public var methods: [HTTPMethod] = [.get, .post, .put] 42 | 43 | // 允许的自定义头数据 44 | public var customHeaders = [String]() 45 | 46 | // Access-Control-Allow-Credentials 是否允许访问机密信息 47 | // 默认情况下标准 CORS 请求不会发送或者设置任何cookies。 48 | // 如果希望允许在跨域请求中使用cookies,则请将此处设置为真。 49 | public var withCredentials = false 50 | 51 | // 内容缓冲时限,单位时秒 52 | // 默认为0,也就是关闭内容缓冲 53 | public var maxAge = 3600 54 | 55 | ``` 56 | 57 | 当一个 CORS 请求发到服务器后,如果选项配置不匹配任何允许的主机,则该配置会提醒浏览器不要接受这种资源。 58 | 59 | 如果服务器决定产生 CORS 头数据,则下列头数据会追加到响应内容: 60 | 61 | ``` swift 62 | // 允许的 HTTP 方法。 63 | // 由上述配置数组生成的内容: 64 | Access-Control-Allow-Methods: GET, POST, PUT 65 | 66 | // 如果来源是被认可的,则来源会被自动回应给请求者 67 | // (即使配置为通配符 *) 68 | Access-Control-Allow-Origin: http://www.test-cors.org 69 | 70 | // 如果服务器允许cookies 71 | Access-Control-Allow-Credentials: true 72 | 73 | // 允许在服务器上设置缓冲(有效期为1个小时) 74 | Access-Control-Max-Age: 3600 75 | ``` 76 | 77 | 可以参考以下工具来验证 CORS 冠名和响应结果[http://www.test-cors.org](http://www.test-cors.org) -------------------------------------------------------------------------------- /guide.zh_CN/dir.md: -------------------------------------------------------------------------------- 1 | # 目录与路径 2 | 3 | Perfect为服务器端的Swift语言环境提供了一个管理文件存储的便捷方法。 4 | 5 | 首先,请在源程序代码开始部分声明`PerfectLib`函数库: 6 | 7 | ``` swift 8 | import PerfectLib 9 | ``` 10 | 声明后您就随时可以使用`Dir`目录对象查询和操作文件系统。 11 | 12 | ### 设置一个目录对象参考指针 13 | 14 | 使用目录对象时,需要指定目录的绝对或相对路径: 15 | 16 | ``` swift 17 | let thisDir = Dir("/path/to/directory/") 18 | ``` 19 | 20 | ### 检查目录是否存在 21 | 22 | 使用`exists`方法检查目录是否存在。返回结果是一个布尔值,真值表示目录存在,假值表示不存在。 23 | 24 | ``` swift 25 | let thisDir = Dir("/path/to/directory/") 26 | thisDir.exists 27 | ``` 28 | 29 | ### 返回当前目录对象的名称 30 | 31 | 调用`name`方法可以返回当前目录对象的名称。注意名称不是路径,二者并不相同! 32 | 33 | ``` swift 34 | thisDir.name 35 | ``` 36 | 37 | ### 返回到上一级目录 38 | 39 | 调用`parentDir`方法可以得到当前`Dir`目录对象所指向上一级目录。如果不存在上一级目录,则返回nil。 40 | 41 | ``` swift 42 | let thisDir = Dir("/path/to/directory/") 43 | let parent = thisDir.parentDir 44 | ``` 45 | 46 | ### 显示当前目录对象的路径 47 | 48 | 调用`path`方法可以得到当前目录对象所在的路径。 49 | 50 | ``` swift 51 | let thisDir = Dir("/path/to/directory/") 52 | let path = thisDir.path 53 | ``` 54 | 55 | ### 返回当前目录的UNIX权限 56 | 57 | 调用`perms`方法返回UNIX风格的目录权限信息,返回值为一个`PermissionMode`目录权限对象 58 | 59 | ``` swift 60 | thisDir.perms 61 | ``` 62 | 63 | 比如: 64 | 65 | ``` swift 66 | print(thisDir.perms) 67 | >> PermissionMode(rawValue: 29092) 68 | ``` 69 | 70 | ### 创建一个目录 71 | 72 | 根据用户提供的权限信息创建一个目录。沿该路径下的所有目录都会根据需要一并创建。 73 | 74 | 以下操作将采用默认权限(Owner目录所有者用户具有读、写、执行权限,用户组和其它用户具有读和执行权限)创建一个新的目录。 75 | 76 | ``` swift 77 | let newDir = Dir("/path/to/directory/newDirectory") 78 | try newDir.create() 79 | ``` 80 | 81 | 如果在创建目录时需要指定权限信息,请在调用前填写`perms`参数: 82 | 83 | ``` swift 84 | let newDir = Dir("/path/to/directory/newDirectory") 85 | try newDir.create(perms: [.rwxUser, .rxGroup, .rxOther]) 86 | ``` 87 | 88 | 如果创建目录过程中出现错误,该方法将抛出`PerfectError.FileError`错误。 89 | 90 | 91 | ### 删除一个目录 92 | 93 | 从文件系统中删除目录的方法: 94 | 95 | ``` swift 96 | let newDir = Dir("/path/to/directory/newDirectory") 97 | try newDir.delete() 98 | ``` 99 | 100 | 如果在删除过程中出现错误,该方法会抛出`PerfectError.FileError`错误信息。 101 | 102 | ### 工作路径 103 | 104 | ### 改变当前目录对象目标指向的当前路径 105 | 106 | 请使用`setAsWorkingDir`来设置当前目录对象所指向的工作路径。 107 | 108 | ``` swift 109 | let thisDir = Dir("/path/to/directory/") 110 | try thisDir.setAsWorkingDir() 111 | ``` 112 | 113 | ### 返回当前工作路径 114 | 115 | 返回一个新的目录对象,内容包含当前工作路径。 116 | 117 | ``` swift 118 | let workingDir = Dir.workingDir 119 | ``` 120 | 121 | ### 读取目录树结构 122 | 123 | 请以闭包为回调参数调用`forEachEntry`来遍历目录下的每一个节点,包括文件和子目录。 124 | 125 | ``` swift 126 | try thisDir.forEachEntry(closure: { 127 | n in 128 | print(n) 129 | }) 130 | ``` 131 | -------------------------------------------------------------------------------- /guide/Redis.md: -------------------------------------------------------------------------------- 1 | # Redis 2 | 3 | Redis is an open source (BSD licensed), in-memory data structure store, used as database, cache, and message broker. 4 | 5 | More info at [http://redis.io](http://redis.io) 6 | 7 | 8 | ## Quick Start 9 | 10 | Get a redis client with defaults: 11 | 12 | ```swift 13 | RedisClient.getClient(withIdentifier: RedisClientIdentifier()) { 14 | c in 15 | do { 16 | let client = try c() 17 | ... 18 | } catch { 19 | ... 20 | } 21 | } 22 | ``` 23 | 24 | Ping the server: 25 | 26 | ```swift 27 | client.ping { 28 | response in 29 | defer { 30 | RedisClient.releaseClient(client) 31 | } 32 | guard case .simpleString(let s) = response else { 33 | ... 34 | return 35 | } 36 | XCTAssert(s == "PONG", "Unexpected response \(response)") 37 | } 38 | ``` 39 | 40 | Set/get a value: 41 | 42 | ```swift 43 | let (key, value) = ("mykey", "myvalue") 44 | client.set(key: key, value: .string(value)) { 45 | response in 46 | guard case .simpleString(let s) = response else { 47 | ... 48 | return 49 | } 50 | client.get(key: key) { 51 | response in 52 | defer { 53 | RedisClient.releaseClient(client) 54 | } 55 | guard case .bulkString = response else { 56 | ... 57 | return 58 | } 59 | let s = response.toString() 60 | XCTAssert(s == value, "Unexpected response \(response)") 61 | } 62 | } 63 | ``` 64 | 65 | Pub/sub: 66 | 67 | ```swift 68 | RedisClient.getClient(withIdentifier: RedisClientIdentifier()) { 69 | c in 70 | do { 71 | let client1 = try c() 72 | RedisClient.getClient(withIdentifier: RedisClientIdentifier()) { 73 | c in 74 | do { 75 | let client2 = try c() 76 | client1.subscribe(channels: ["foo"]) { 77 | response in 78 | client2.publish(channel: "foo", message: .string("Hello!")) { 79 | response in 80 | client1.readPublished(timeoutSeconds: 5.0) { 81 | response in 82 | guard case .array(let array) = response else { 83 | ... 84 | return 85 | } 86 | XCTAssert(array.count == 3, "Invalid array elements") 87 | XCTAssert(array[0].toString() == "message") 88 | XCTAssert(array[1].toString() == "foo") 89 | XCTAssert(array[2].toString() == "Hello!") 90 | } 91 | } 92 | } 93 | } catch { 94 | ... 95 | } 96 | } 97 | } catch { 98 | ... 99 | } 100 | } 101 | ``` 102 | 103 | ## Building 104 | 105 | Add this project as a dependency in your Package.swift file. 106 | 107 | ``` 108 | .Package(url: "https://github.com/PerfectlySoft/Perfect-Redis.git", majorVersion: 3) 109 | ``` 110 | 111 | -------------------------------------------------------------------------------- /guide.zh_CN/sysProcess.md: -------------------------------------------------------------------------------- 1 | # SysProcess系统进程 2 | 3 | 在Perfect环境下,用户可以通过`SysProcess`调用系统命令,并允许在调用进程是自定义配套的参数数组和环境变量。部分进程可以立刻被执行并返回结果;其它一些进程则可以处于开通状态,随时可以进行互动性数据读写。 4 | 5 | ### 设置 6 | 7 | 请在您的"Perfect"项目中打开Package.swift设置依存关系: 8 | 9 | ``` swift 10 | .Package( 11 | url: "https://github.com/PerfectlySoft/Perfect.git", 12 | majorVersion: 3 13 | ) 14 | ``` 15 | 在具体需要调用系统进程的源代码文件开头,声明PerfectLib库文件并增加linux下SwiftGlibc或macOS的Darwin编译条件: 16 | 17 | ``` swift 18 | import PerfectLib 19 | 20 | #if os(Linux) 21 | import SwiftGlibc 22 | #else 23 | import Darwin 24 | #endif 25 | ``` 26 | 27 | ### 执行系统进程调用命令 28 | 29 | 函数方法`runProc`可以用于带参数数组进行调用系统命令,并可选择是否从命令返回中输出响应结果。 30 | 31 | ``` swift 32 | func runProc(cmd: String, args: [String], read: Bool = false) throws -> String? { 33 | let envs = [("PATH", "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin")] 34 | let proc = try SysProcess(cmd, args: args, env: envs) 35 | var ret: String? 36 | if read { 37 | var ary = [UInt8]() 38 | while true { 39 | do { 40 | guard let s = try proc.stdout?.readSomeBytes(count: 1024) where s.count > 0 else { 41 | break 42 | } 43 | ary.append(contentsOf: s) 44 | } catch PerfectLib.PerfectError.fileError(let code, _) { 45 | if code != EINTR { 46 | break 47 | } 48 | } 49 | } 50 | ret = UTF8Encoding.encode(bytes: ary) 51 | } 52 | let res = try proc.wait(hang: true) 53 | if res != 0 { 54 | let s = try proc.stderr?.readString() 55 | throw PerfectError.systemError(Int32(res), s!) 56 | } 57 | return ret 58 | } 59 | 60 | let output = try runProc(cmd: "ls", args: ["-la"], read: true) 61 | print(output) 62 | ``` 63 | 64 | 注意例子中的`SysProcess`命令所带环境变量并非在所有系统内通用(请用户根据宿主系统自行输入正确的环境变量以验证本例)。 65 | 66 | ### SysProcess系统进程类成员字段 67 | 68 | #### stdin 69 | `stdin`即当前文件系统的标准输入流。 70 | 71 | #### stdout 72 | `stdout` 即当前文件系统的标准输出流。 73 | 74 | #### stderr 75 | `stderr` 即当前文件系统的标准错误流。 76 | 77 | #### pid 78 | `pid` 即进程唯一标示符 79 | 80 | ### SysProcess系统进程类成员函数 81 | 82 | #### isOpen 83 | 84 | 如果当前进程仍处于“开放”状态(即输入输出可读写),则返回真值 85 | 86 | 注意当前进程不一定处于运行状态,请用`wait(false)` 方法来检查当前进程是否处于运行状态。 87 | 88 | ``` swift 89 | myProcess.isOpen() 90 | ``` 91 | 92 | #### close 93 | 94 | `close` 关闭进程并清理内容。 95 | 96 | ``` swift 97 | myProcess.close() 98 | ``` 99 | 100 | #### detatch 101 | 102 | 从进程中脱离,以确保即便是该进程进入僵尸态后资源也能够自动释放(即无需再等待进程返回) 103 | 104 | ``` swift 105 | myProcess.detatch() 106 | ``` 107 | 108 | #### wait 109 | 110 | 判断该进程是否已经结束运行并读取其返回值。 111 | 112 | ``` swift 113 | myProcess.wait(hang: ) 114 | ``` 115 | 116 | #### kill 117 | 118 | 终止进程并读取其返回值。 119 | 120 | ``` swift 121 | myProcess.kill(signal: ) 122 | ``` 123 | 124 | 返回值是一个`Int32`整型代码 125 | -------------------------------------------------------------------------------- /guide.zh_CN/HTTPRequestLogging.md: -------------------------------------------------------------------------------- 1 | # HTTP 请求的日志记录 2 | 3 | 如果希望把HTTP请求写入日志文件,请使用`Perfect-RequestLogger` 函数库 4 | 5 | ## 相关范例 6 | 7 | * [Perfect-HTTPRequestLogging](https://github.com/PerfectExamples/Perfect-HTTPRequestLogging) 8 | * [Perfect-Session-Memory-Demo](https://github.com/PerfectExamples/Perfect-Session-Memory-Demo) 9 | 10 | ## 使用方法 11 | 12 | 请在您的项目文件 `Package.swift` 中增加以下依存关系: 13 | 14 | ```swift 15 | .Package(url: "https://github.com/PerfectlySoft/Perfect-RequestLogger.git", majorVersion: 3) 16 | ``` 17 | 18 | 对于要调用该功能的源程序,请在源程序文件开头增加导入语句: 19 | 20 | ``` swift 21 | import PerfectRequestLogger 22 | ``` 23 | 24 | ## 对于 PerfectHTTP 2.1 以上版本 25 | 26 | 在您服务器 `main.swift` 增加下列内容 27 | 28 | ```swift 29 | // 初始化日志记录器 30 | let httplogger = RequestLogger() 31 | 32 | // 配置服务器 33 | var confData: [String:[[String:Any]]] = [ 34 | "servers": [ 35 | [ 36 | "name":"localhost", 37 | "port":8181, 38 | "routes":[], 39 | "filters":[ 40 | [ 41 | "type":"response", 42 | "priority":"high", 43 | "name":PerfectHTTPServer.HTTPFilter.contentCompression, 44 | ], 45 | [ 46 | "type":"request", 47 | "priority":"high", 48 | "name":RequestLogger.filterAPIRequest, 49 | ], 50 | [ 51 | "type":"response", 52 | "priority":"low", 53 | "name":RequestLogger.filterAPIResponse, 54 | ] 55 | ] 56 | ] 57 | ] 58 | ] 59 | ``` 60 | 其中配置关键在于增加下列过滤器: 61 | 62 | ``` swift 63 | [ 64 | "type":"request", 65 | "priority":"high", 66 | "name":RequestLogger.filterAPIRequest, 67 | ], 68 | [ 69 | "type":"response", 70 | "priority":"low", 71 | "name":RequestLogger.filterAPIResponse, 72 | ] 73 | ``` 74 | 这些请求/响应过滤器能够用于触发HTTP访问时记录日志。 75 | 76 | 77 | 78 | ## 如果使用 PerfectHTTP 2.0 版本服务器 79 | 80 | 在您服务器 `main.swift` 增加下列内容 81 | 82 | ```swift 83 | // 初始化日志记录器 84 | let httplogger = RequestLogger() 85 | 86 | // 增加过滤器 87 | // 请求过滤器,高优先触发 88 | server.setRequestFilters([(httplogger, .high)]) 89 | // 响应过滤器,最后一个触发 90 | server.setResponseFilters([(httplogger, .low)]) 91 | ``` 92 | 93 | 这些请求/响应过滤器能够用于触发HTTP访问时记录日志。 94 | 95 | ## 设置日志文件的存储位置 96 | 97 | 默认的服务器日志文件路径为`/var/log/perfectLog.log`。您可以通过在主程序`main.swift`中设置属性 `RequestLogFile.location`: 98 | 99 | ``` swift 100 | RequestLogFile.location = "/var/log/myLog.log" 101 | ``` 102 | 103 | ## 输出效果: 104 | 105 | ``` 106 | [INFO] [62f940aa-f204-43ed-9934-166896eda21c] [servername/WuAyNIIU-1] 2016-10-07 21:49:04 +0000 "GET /one HTTP/1.1" from 127.0.0.1 - 200 64B in 0.000436007976531982s 107 | [INFO] [ec6a9ca5-00b1-4656-9e4c-ddecae8dde02] [servername/WuAyNIIU-2] 2016-10-07 21:49:06 +0000 "GET /two HTTP/1.1" from 127.0.0.1 - 200 64B in 0.000207006931304932s 108 | ``` 109 | 110 | 该模块是在大卫·弗莱明的工作基础之上完成:[David Fleming](https://github.com/dabfleming). 111 | -------------------------------------------------------------------------------- /guide/logFiles.md: -------------------------------------------------------------------------------- 1 | # File Logging 2 | 3 | Using the `PerfectLogger` module, events can be logged to a specified file, in addition to the console. 4 | 5 | ## Usage 6 | 7 | Add the dependency to your project's Package.swift file: 8 | 9 | ``` swift 10 | .Package(url: "https://github.com/PerfectlySoft/Perfect-Logger.git", majorVersion: 3), 11 | ``` 12 | 13 | Now add the `import` directive to the file you wish to use the logging in: 14 | 15 | ``` swift 16 | import PerfectLogger 17 | ``` 18 | 19 | To log events to the local console as well as a file: 20 | 21 | ``` swift 22 | LogFile.debug("debug message", logFile: "test.txt") 23 | LogFile.info("info message", logFile: "test.txt") 24 | LogFile.warning("warning message", logFile: "test.txt") 25 | LogFile.error("error message", logFile: "test.txt") 26 | LogFile.critical("critical message", logFile: "test.txt") 27 | LogFile.terminal("terminal message", logFile: "test.txt") 28 | ``` 29 | 30 | To log to the default file, omit the file name parameter. 31 | 32 | ## Linking events with "eventid" 33 | 34 | Each log event returns an event id string. If an eventid string is supplied to the directive then it will use the supplied eventid in the log file instead. This makes it easy to link together related events. 35 | 36 | ``` swift 37 | let eid = LogFile.warning("test 1") 38 | LogFile.critical("test 2", eventid: eid) 39 | ``` 40 | 41 | returns: 42 | 43 | ``` 44 | [WARNING] [62f940aa-f204-43ed-9934-166896eda21c] [2016-11-16 15:18:02 GMT-05:00] test 1 45 | [CRITICAL] [62f940aa-f204-43ed-9934-166896eda21c] [2016-11-16 15:18:02 GMT-05:00] test 2 46 | ``` 47 | 48 | The returned eventid is marked `@discardableResult` and therefore can be safely ignored if not required for re-use. 49 | 50 | 51 | ## Setting a custom Logfile location 52 | 53 | The default logfile location is `./log.log`. To set a custom logfile location, set the `LogFile.location` variable: 54 | 55 | ``` swift 56 | LogFile.location = "/var/log/myLog.log" 57 | ``` 58 | 59 | Messages can now be logged directly to the file as set by using: 60 | 61 | ``` swift 62 | LogFile.debug("debug message") 63 | LogFile.info("info message") 64 | LogFile.warning("warning message") 65 | LogFile.error("error message") 66 | LogFile.critical("critical message") 67 | LogFile.terminal("terminal message") 68 | ``` 69 | 70 | ## Sample output 71 | 72 | ``` 73 | [DEBUG] [ec6a9ca5-00b1-4656-9e4c-ddecae8dde02] [2016-11-16 15:18:02 GMT-05:00] a debug message 74 | [INFO] [ec6a9ca5-00b1-4656-9e4c-ddecae8dde02] [2016-11-16 15:18:02 GMT-05:00] an informational message 75 | [WARNING] [ec6a9ca5-00b1-4656-9e4c-ddecae8dde02] [2016-11-16 15:18:02 GMT-05:00] a warning message 76 | [ERROR] [62f940aa-f204-43ed-9934-166896eda21c] [2016-11-16 15:18:02 GMT-05:00] an error message 77 | [CRITICAL] [62f940aa-f204-43ed-9934-166896eda21c] [2016-11-16 15:18:02 GMT-05:00] a critical message 78 | [EMERG] [ec6a9ca5-00b1-4656-9e4c-ddecae8dde02] [2016-11-16 15:18:02 GMT-05:00] an emergency message 79 | ``` 80 | -------------------------------------------------------------------------------- /guide.zh_CN/StORM-Setting-up-a-class.md: -------------------------------------------------------------------------------- 1 | # 使用 StORM 数据类 2 | 3 | 首先请将函数库引入程序: 4 | 5 | ``` swift 6 | import PostgresStORM 7 | // 如果您用的其他数据源,比如SQLite,请这样声明: 8 | import SQLiteStORM 9 | ``` 10 | 11 | 当您在 StORM 基础上创建数据类的时候,会触发数据库自动构造一个表格,使得与数据库操作实现自动化。 12 | 13 | 如果您使用的是 PostgreSQL,请以 PostgresStORM 作为基类声明: 14 | 15 | ``` swift 16 | class User: PostgresStORM { 17 | } 18 | ``` 19 | 20 | 如果您使用的是 SQLite,请以 SQLiteStORM 作为基类声明: 21 | 22 | ``` swift 23 | class User: SQLiteStORM { 24 | } 25 | ``` 26 | 27 | 下一步,请为您的类增加属性,该操作将自动更新相关的数据表结构。在此强烈建议在编写属性部分程序的时候,请不要使用非标准字符,因为可能数据库不接受这样的字段,比如“🍒”。 28 | 29 | *⚠️注意⚠️* 该对象的第一个属性将成为对应数据表的主索引 —— 传统的方式就是给主索引列起名叫做 `id`,虽然您可以为主索引字段设置任何有效的名字。SQL这种关系数据库的主索引典型类型是整型、字符串或者UUID编码。如果您的主索引不是自动递增的整数,则一定要设置好这个id值,以保证数据的完整性和一致性。 30 | 31 | ``` swift 32 | // ⚠️注意⚠️:第一个属性将成为主索引字段,所以应该是ID。 33 | var id : Int = 0 34 | var firstname : String = "" 35 | var lastname : String = "" 36 | var email : String = "" 37 | ``` 38 | 39 | 您可能已经注意到上面的程序通过给每个字段设置默认值,而不是在`init()`构造函数中设置。这样看起来更简单一些。如果您选择自行编写`init()` 或 `init(_ connect: XXConnect)` 构造函数,请注意一定要在构造函数中首先调用基类的构造函数`super.init()`,而且要小心数据库的连接属性。 40 | 41 | ### 确定数据表格 42 | 43 | 为了避免数据库内的命名冲突,请用下列方法自行设置每个数据类的对应数据表名称,如下所示: 44 | 45 | ``` swift 46 | override open func table() -> String { 47 | return "users" 48 | } 49 | ``` 50 | 51 | ### 数据类对象与数据库的属性绑定 52 | 53 | 为了能够让新编写的数据类正常工作,比如查询数据并把结果记录集转换成为对象数组等等,请一定要在您的数据类中增加以下两个函数: 54 | 55 | ``` swift 56 | override func to(_ this: StORMRow) { 57 | id = this.data["id"] as! Int 58 | firstname = this.data["firstname"] as! String 59 | lastname = this.data["lastname"] as! String 60 | email = this.data["email"] as! String 61 | } 62 | 63 | func rows() -> [User] { 64 | var rows = [User]() 65 | for i in 0.. String { 96 | return "users" 97 | } 98 | 99 | override func to(_ this: StORMRow) { 100 | id = this.data["id"] as! Int 101 | firstname = this.data["firstname"] as! String 102 | lastname = this.data["lastname"] as! String 103 | email = this.data["email"] as! String 104 | } 105 | 106 | func rows() -> [User] { 107 | var rows = [User]() 108 | for i in 0.. 41 | 42 | ``` swift 43 | zipFiles( 44 | paths: [String], 45 | zipFilePath: String, 46 | overwrite: Bool, 47 | password: String? 48 | ) -> ZipStatus 49 | ``` 50 | 51 | 该方法调用后会返回一个`ZipStatus`枚举变量,用于说明压缩操作是成功还是失败。 52 | 53 | #### 参数说明: 54 | 55 | * **paths:** 用于保存待压缩文件所在路径信息的数组 56 | * **zipFilePath:** 压缩后文件的保存路径 57 | * **overwrite:** 如果压缩后的文件已经存在,本次压缩是否将已存在文件。此处如果设置为真,则覆盖;否则忽略。 58 | * **password:** 可选参数;如果需要对该压缩文件设置密码保护,则该字符串用于存储密码。 59 | 60 | 61 | ### UnZip 解压缩 62 | 63 | 如果要解压缩一个文件,请使用`.unzipFile(...)`方法 64 | 65 | ``` swift 66 | unzipFile( 67 | source: String, 68 | destination: String, 69 | overwrite: Bool, 70 | password: String = "" 71 | ) -> ZipStatus 72 | ``` 73 | 74 | 该方法调用后会返回一个`ZipStatus`枚举变量,用于说明解压缩操作是成功还是失败。 75 | 76 | #### 参数说明 77 | 78 | * **source:** 压缩文件的路径信息 79 | * **destination:** 解压缩文件的目标路径 80 | * **overwrite:** 如果目标路径已存在,是否需要覆盖。如果此处变量设置为真则覆盖;否则忽略 81 | * **password:** 可选参数;用于解压缩的密码。 82 | 83 | 84 | ### ZipStatus 操作结果状态 85 | 86 | 压缩/解压操作返回结果枚举值 `ZipStatus` 参考如下: 87 | 88 | * .FileNotFound 89 | * .UnzipFail 90 | * .ZipFail 91 | * .ZipCannotOverwrite 92 | * .ZipSuccess 93 | 94 | 该枚举类型还包括一个 `.description` 变量,用于返回每个返回值所代表的具体含义。 95 | 96 | * .FileNotFound - **"文件不存在"** 97 | * .UnzipFail - **"解压缩失败"** 98 | * .ZipFail - **"压缩失败"** 99 | * .ZipCannotOverwrite - **"无法覆盖目标文件"** 100 | * .ZipSuccess - **"操作成功"** 101 | 102 | 103 | 104 | ## 使用方法 105 | 106 | 下面的源代码演示了如何压缩一个文件 107 | 108 | ``` swift 109 | import PerfectZip 110 | 111 | let myZip = Zip() 112 | 113 | let thisZipFile = "/path/to/ZipFile.zip" 114 | let sourceDir = "/path/to/files/" 115 | 116 | let ZipResult = myZip.zipFiles( 117 | paths: [sourceDir], 118 | zipFilePath: thisZipFile, 119 | overwrite: true, password: "" 120 | ) 121 | print("压缩结果:\(ZipResult.description)") 122 | ``` 123 | 124 | 下面的源代码演示了如何解压缩一个文件 125 | 126 | ``` swift 127 | import PerfectZip 128 | 129 | let myZip = Zip() 130 | 131 | let sourceDir = "/path/to/files/" 132 | let thisZipFile = "/path/to/ZipFile.zip" 133 | 134 | let UnZipResult = myZip.unzipFile( 135 | source: thisZipFile, 136 | destination: sourceDir, 137 | overwrite: true 138 | ) 139 | print("解压缩结果:\(UnZipResult.description)") 140 | ``` 141 | -------------------------------------------------------------------------------- /guide.zh_CN/SPNEGO.md: -------------------------------------------------------------------------------- 1 | # Perfect-SPNEGO [English](README.md) 2 | 3 | 该项目实现了一个用于 Swift 服务器编程的 SPNEGO 安全机制函数库 4 | 5 | ### 开始之前 6 | 7 | Perfect SPNEGO 瞄准的是所有用Swift 编程的通用服务器组件,因此能够应用于⚠️任何⚠️服务器,比如 HTTP / FTP / SSH,等等。 8 | 9 | 尽管该服务器组件能够原生支持 Perfect 自己的 HTTP 服务器,但是实际上也适用于任何一种其他互联网协议的服务器。 10 | 11 | 在安装到目标服务器软件之前,请确认您的服务器已经完成了Kerberos V的配置。 12 | 13 | ### Xcode 编译指南 14 | 15 | 如果您希望使用Xcode 编译本项目,请使用将特殊的符号传递给 SPM 软件包管理器: 16 | 17 | ``` 18 | $ swift package -Xlinker -framework -Xlinker GSS generate-xcodeproj 19 | ``` 20 | 21 | ### Linux 编译指南 22 | 23 | 函数库 libkrb5-dev 需要在编译之前安装: 24 | 25 | ``` 26 | $ sudo apt-get install libkrb5-dev 27 | ``` 28 | 29 | 如果您的服务器是一个KDC(安全控制中心),您可以忽略这一步,否则请安装 Kerberos V5 工具组件,以及开发函数库: 30 | 31 | ``` 32 | $ sudo apt-get install krb5-user 33 | ``` 34 | 35 | ### KDC配置 36 | 37 | 请配置好您的应用服务器/etc/krb5.conf,以便于该服务器能够正常连接到目标的KDC。请参考下面的例子,在这个例子中,应用服务器希望连接到控制区域`KRB5.CA`,并且该控制区域的KDC控制中心域名为`nut.krb5.ca`: 38 | 39 | ``` 40 | [realms] 41 | KRB5.CA = { 42 | kdc = nut.krb5.ca 43 | admin_server = nut.krb5.ca 44 | } 45 | [domain_realm] 46 | .krb5.ca = KRB5.CA 47 | krb5.ca = KRB5.CA 48 | ``` 49 | 50 | ### 准备Kerberos 授权钥匙 51 | 52 | 下一步需要联系您的KDC 管理员,获得应用服务器将使用的keytab钥匙文件。 53 | 54 | 参考下面的例子的示范配置,⚠️下列所有主机必须注册到同一个DNS⚠️ 55 | 56 | - KDC 安全控制中心服务器: nut.krb5.ca 57 | - 应用服务器: coco.krb5.ca 58 | - 应用服务器计划安装的协议类型: HTTP 59 | 60 | 如果处于上述环境,则KDC管理员需要登录`nut.krb5.ca` 控制区域并采取下列操作: 61 | 62 | ``` 63 | kadmin.local: addprinc -randkey HTTP/coco.krb5.ca@KRB5.CA 64 | kadmin.local: ktadd -k /tmp/krb5.keytab HTTP/coco.krb5.ca@KRB5.CA 65 | ``` 66 | 67 | 生成钥匙文件krb5.keytab后,请将该钥匙安全地传输到您的应用服务器`coco.krb5.ca`并将文件移动到目录`/etc`下,然后赋予其适当权限,以便于您的服务器可以访问到。 68 | 69 | ## 快速开始 70 | 71 | 首先在您的软件工程配置文件Package.swift中增加下列依存关系: 72 | 73 | ``` swift 74 | .Package(url: "https://github.com/PerfectlySoft/Perfect-SPNEGO.git", majorVersion: 1) 75 | ``` 76 | 77 | 然后将 Perfect-SPNEGO 引入源代码: 78 | 79 | ``` swift 80 | import PerfectSPNEGO 81 | ``` 82 | 83 | ### 在应用程序中连接到 KDC 84 | 85 | 请使用默认钥匙文件 `/etc/krb5.keytab` 中列出的服务器名称,然后在程序中注册到KDC: 86 | 87 | ``` swift 88 | let spnego = try Spnego("HTTP@coco.krb5.ca") 89 | ``` 90 | 91 | 请注意主机名和协议⚠️务必⚠️与钥匙文件中的内容保持完全一致。 92 | 93 | ### 响应 SPNEGO 挑战 94 | 95 | 初始化成功之后,您的应用程序即可使用`spnego`对象响应此类挑战。比如,如果用户正尝试访问应用服务器: 96 | 97 | ``` 98 | $ kinit rocky@KRB5.CA 99 | $ curl --negotiate -u : http://coco.krb5.ca 100 | ``` 101 | 102 | 这种情况下,curl命令行将在其HTTP请求头数据中发送一个 base64 编码的申请表: 103 | 104 | ``` 105 | > Authorization: Negotiate YIICnQYGKwYBBQUCoIICkTCCAo2gJzAlBgkqhkiG9xIBAgI ... 106 | ``` 107 | 108 | 您的服务器一旦收到这个申请表,即可使用 `spnego` 对象进行解码分析: 109 | ``` 110 | let (user, response) = try spnego.accept(base64Token: "YIICnQYGKwYBBQUCoIICkTCCAo2gJzAlBgkqhkiG9xIBAgI...") 111 | ``` 112 | 113 | 如果成功的话,用户名称将会是"rocky@KRB5.CA"。而变量`response`有可能为空,表示这种申请不需要回复多余的数据;否则请将这个同样是base64的字符串返回给客户。 114 | 115 | 到此为止,您的程序已经获得了用户信息,您的应用程序可以根据这个验证过的用户名称,查看ACL(安全控制表)来决定是否允许该用户访问其期待的资源。 116 | 117 | ## 有关示例 118 | 119 | 以下连接包括了一个如何在 Perfect HTTP 服务器中应用 SPNEGO 安全机制的范例: 120 | * [Perfect-Spnego-Demo](https://github.com/PerfectExamples/Perfect-Spnego-Demo) -------------------------------------------------------------------------------- /guide.zh_CN/StORM-Saving-Retrieving-and-Deleting-Rows.md: -------------------------------------------------------------------------------- 1 | # 数据记录的创建、保存、查询和删除 2 | 3 | 在查看本章内容时,请首先查看关于[设置数据类](https://github.com/PerfectlySoft/PerfectDocs/blob/master/guide.zh_CN/StORM/Setting-up-a-class.md)中的“User”数据类 4 | 5 | ## 创建并更新结果记录集 6 | 7 | 创建和更新数据的操作非常类似:如果主索引字段被设置为0或空,`save`方法会自动增加一个数据行(SQL INSERT操作),否则会进行数据更新(SQL UPDATE操作)。 8 | 9 | 创建一个新数据行: 10 | 11 | ``` swift 12 | let obj = User(connect) 13 | obj.firstname = "Joe" 14 | obj.lastname = "Smith" 15 | 16 | try obj.save { 17 | id in obj.id = id as! Int 18 | } 19 | ``` 20 | 21 | 注意新数据行中的属性`.email`在开始保存的时候被忽略了。此时,如果填写这个属性并在此保存: 22 | 23 | ``` swift 24 | email = "joe.smith@example.com" 25 | try obj.save() 26 | ``` 27 | 28 | 因为 `.id` 属性已经被设置了,这时的数据保存则是一个 update 操作。 29 | 30 | ### 在主索引存在的情况下创建一个新数据记录 31 | 32 | 很多时候您希望手工设置主索引字段,在这种情况下,一旦改变了主索引字段的属性值,上面的保存工作方式就失效了。 33 | 34 | 因此,这种情况下请调用`.create()`方法。 35 | 36 | 该方法强制调用一个INSERT操作,并使用您主动设置的主索引值。 37 | 38 | ``` swift 39 | let obj = User(connect) 40 | obj.id = 10001 41 | obj.firstname = "Mister" 42 | obj.lastname = "PotatoHead" 43 | obj.email = "potato@example.com" 44 | try obj.create() 45 | ``` 46 | 47 | ### 错误捕获 48 | 49 | `.save`方法调用时可以使用`try`语法,但是 *⚠️注意⚠️* 捕获到的结果是可以被忽略的。尽管如此,有针对性的错误处理总是一个好习惯。 50 | 51 | 前文提到的`.save`可以按照如下方法进行处理: 52 | 53 | ``` swift 54 | do { 55 | try obj.save {id in obj.id = id as! Int } 56 | } catch { 57 | // 在控制台输出错误异常 58 | print("发现错误 \(error)") 59 | // 在此处进行错误处理。 60 | } 61 | ``` 62 | 63 | ## 查询数据 64 | 65 | 有三种方法可以用于查询一行或多行结果记录集:`.get`、`.find`和`.select` 66 | 67 | ### Get 方法 68 | 69 | `.get`方法能够用于通过主索引值直接提取具体的数据记录。 70 | 71 | ``` swift 72 | let obj = User(connect) 73 | try obj.get(1) 74 | print("用户姓名: \(obj.firstname) \(obj.lastname)") 75 | ``` 76 | 77 | 用户的 id 标识也可以在 `.get` 操作之前设置。 78 | 79 | ``` swift 80 | let obj = User(connect) 81 | obj.id = 2 82 | try obj.get() 83 | print("用户姓名:\(obj.firstname) \(obj.lastname)") 84 | ``` 85 | 86 | ### Find 方法 87 | 88 | `.find` 方法会按照字段/值的方法精确匹配一个查询结果记录集。 89 | 90 | ``` swift 91 | let obj = User(connect) 92 | try obj.find([("firstname", "Joe")]) 93 | print("找到记录: \(obj.id), \(obj.firstname), \(obj.lastname)") 94 | ``` 95 | 96 | ### 内建的 SELECT 方法 97 | 98 | 更强大的查询功能是`.select`方法,用于获得更加复杂的查询操作。 99 | 100 | select 方法能够接受几种不同的输入形式,最简单的一种是: 101 | 102 | ``` swift 103 | try obj.select( 104 | whereclause: "firstname = $1", 105 | params: ["Joe"], 106 | orderby: ["id"] 107 | ) 108 | ``` 109 | 110 | The `.select` 方式的好处是能够防止绑定到项目的参数被黑客进行SQL注入。尽管所有的方法都是能够达到防范目的,但是只有少数核心方法需要您了解参数绑定是如何操作的。 111 | 112 | 前面的例子中,参数`$1`用于告诉计算机该参数将会被替换为参数数组 `.params` 的第一个元素。替换参数将保护数据类型不被滥用。 113 | 114 | 而`.select`方法允许更多精准的检索,比如 LIKE 和其他逻辑比较运算符。这方面您需要事先了解以下 SQL 的基础知识。 115 | 116 | `.select` 方法还包括一些选项: 117 | 118 | ``` swift 119 | columns: [String], 120 | cursor: StORMCursor 121 | ``` 122 | 123 | * `columns` 列数组允许为查询结果集指定字段名。 124 | * `cursor` 是一个 `StORMCursor` 游标对象,用于确定从返回的结果记录集的首记录开始的数据行号,经常用于结果分页输出。 125 | 126 | 127 | ## 删除数据行。 128 | 129 | 删除数据的操作和`.get`方法很相似:直接确定待删除的主索引号,或者根据当前数据行号进行删除。 130 | 131 | ``` swift 132 | let obj = User(connect) 133 | try obj.delete(1) 134 | ``` 135 | 136 | ``` swift 137 | let obj = User(connect) 138 | obj.id = 1 139 | try obj.delete() 140 | ``` 141 | -------------------------------------------------------------------------------- /guide.zh_CN/MongoDB.md: -------------------------------------------------------------------------------- 1 | # MongoDB 2 | MongoDB库函数是在mongo-c语言库的基础上封装而成,能够为Swift轻松访问MongoDB服务器提供便利。 3 | 4 | 该工具库软件包是由Swift软件包管理器编译而来,是 5 | [Perfect](https://github.com/PerfectlySoft/Perfect)项目的组成部分, 6 | 被设计为可以独立使用,不依赖PerfectLib或其它任何组件。 7 | 8 | 请确保安装并激活了最新版本的Swift 4.0 toolchain。 9 | 10 | ### 不同操作系统平台的准备工作 11 | 12 | ### OS X 13 | 14 | 该工具包需要通过[Homebrew](http://brew.sh/)安装mongo-c。 15 | 16 | 安装Homebrew: 17 | 18 | ``` 19 | /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" 20 | ``` 21 | 22 | 安装mongo-c: 23 | 24 | ``` 25 | brew install mongo-c-driver 26 | ``` 27 | 28 | ### Linux 29 | 30 | 确保已经安装了libmongoc。 31 | 32 | ``` 33 | sudo apt-get install libmongoc 34 | ``` 35 | 36 | ### 在您的项目里引用MongoDB Driver驱动 37 | 38 | 请在Package.swift增加对该驱动的依存关系。 39 | 40 | ``` swift 41 | .Package( 42 | url:"https://github.com/PerfectlySoft/Perfect-MongoDB.git", 43 | majorVersion: 3 44 | ) 45 | ``` 46 | 47 | 关于如何在您的项目中使用Perfect函数库,详见参考手册《[使用Swift软件包管理器编译项目](buildingWithSPM.md)》 48 | 49 | ### 快速上手 50 | 51 | 通过以下命令快速克隆一个空白的Perfect项目模板: 52 | 53 | ``` 54 | git clone https://github.com/PerfectlySoft/PerfectTemplate.git 55 | cd PerfectTemplate 56 | ``` 57 | 58 | 在Package.swift文件中增加依存关系: 59 | 60 | ```swift 61 | let package = Package( 62 | name: "PerfectTemplate", 63 | targets: [], 64 | dependencies: [ 65 | .Package(url:"https://github.com/PerfectlySoft/Perfect.git", versions: Version(0,0,0).. **⚠️注意⚠️** 每次向项目追加依存关系时,必须要打开Swift软件包管理器重新创建一个新的Xcode项目文件。注意任何对该文件的手工修改都会被丢弃。 82 | 83 | ### 在您的项目中声明MongoDB 84 | 85 | 请在您的Perfect项目源程序开头声明并导入MongoDB函数库: 86 | 87 | ``` swift 88 | import MongoDB 89 | ``` 90 | 91 | ### 创建一个MongoDB数据库连接 92 | 93 | 创建到MongoDB服务器连接时,需要相应的URL,内容是IP或域名,并可选择端口号。 94 | 95 | 确定具体的连接URL之后,参考以下例子打开连接: 96 | 97 | ``` swift 98 | let client = try! MongoClient(uri: "mongodb://localhost") 99 | ``` 100 | 101 | 其中“localhost”请自行替换为实际的服务器地址。 102 | 103 | ### 定义一个数据库 104 | 105 | 一旦服务器连接成功,即可选择具体数据库: 106 | 107 | ``` swift 108 | let db = client.getDatabase(name: "test") 109 | ``` 110 | 111 | ### 定义一个MongoDB集合D 112 | 113 | 请采用以下方式定义和操作MongoDB集合: 114 | 115 | ``` swift 116 | let collection = db.getCollection(name: "testcollection") 117 | ``` 118 | 119 | ### 关闭活动的服务器连接 120 | 121 | 一旦服务器连接成功,建议采用`defer`块方式进行滞后关闭 122 | 123 | ``` swift 124 | defer { 125 | collection.close() 126 | db.close() 127 | client.close() 128 | } 129 | ``` 130 | ### 执行检索 131 | 132 | 请使用`find`方法在集合中检索全部有关文档: 133 | 134 | ``` swift 135 | let fnd = collection.find(query: BSON()) 136 | 137 | // 初始化一个空数组用于接收格式化结果 138 | var arr = [String]() 139 | 140 | // “fnd”被定义为MongoCursor的检索记录游标,是可以遍历的 141 | for x in fnd! { 142 | arr.append(x.asString) 143 | } 144 | 145 | ``` 146 | 147 | 有关MongoDB Collections集合类,请参考[MongoDB Collections](MongoDB-Collections.md)。 148 | -------------------------------------------------------------------------------- /guide.zh_CN/StORM-SQLite.md: -------------------------------------------------------------------------------- 1 | # SQLiteStORM 2 | 3 | ### 在工程中引用 4 | 5 | 请首先在您的 Package.swift 文件中更新依存关系,以确保必要的函数库能够下载到编译环境用于数据库连接: 6 | 7 | ``` swift 8 | .Package(url: "https://github.com/SwiftORM/SQLite-StORM.git", majorVersion: 3) 9 | ``` 10 | ### 关于防范SQL注入式攻击 11 | 12 | StORM 并无法确保您的数据被黑客通过参数化捆绑方式进行SQL注入式攻击。 13 | 14 | 但是在某些函数中,您是完全可以验证SQL查询的语句合法性的,因此,请务必在使用SQL时注意检查外来数据内容。 15 | 16 | ## 创建数据库连接 17 | 18 | 为了创建数据库连接,请自行更新数据源配置: 19 | 20 | ``` swift 21 | SQLiteConnector.db = "./mydb" 22 | ``` 23 | 一旦指定连接的参数,那么类对象就会根据需要自动创建连接。 24 | 25 | ``` swift 26 | let obj = User() 27 | ``` 28 | 29 | ## SQLiteStORM 支持的函数方法 30 | 31 | ### 数据库连接 32 | 33 | `SQLiteConnector.db` - 为 SQLite3 设置数据库连接参数。 34 | 35 | ### 创建数据表 36 | 37 | `setup()` - 通过检查对象直接创建数据表。所有的数据字段会根据该对象的属性自动创建。如果属性以下划线或者 `internal_`命名,则该属性不会作为数据字段进行创建,会被忽略。 38 | 39 | > **⚠️注意⚠️** 其中数据库的 **主索引键** 是该类中的第一个字段。 40 | 41 | ### 将程序中的对象数据保存到数据库 42 | 43 | `save()` - 保存对象。如果当前没有设置主索引键的值,那么就会向数据库内插入一条新的数据。否则如果主索引键已经设置了具体的数值,则会进行一个更新操作。 44 | 45 | `save {id in ... }` - 保存对象。如果当前没有设置主索引键的值,那么就会向数据库内插入一条新的数据。否则如果主索引键已经设置了具体的数值,则会进行一个更新操作。闭包用于如果创建成功的话,返回一个创建完成的数据行id。 46 | 47 | `create()` - 显式要求以插入新数据的方式保存数据,无论主索引键是否已经设置了内容。 48 | 49 | ### 获取数据 50 | 51 | `findAll()` - 获取全部数据行,仅受限于数据游标(9,999,999行) 52 | 53 | `get()` - 假设主索引键已经设置的情况下,获取一行数据记录。 54 | 55 | `get(Any)` - 根据主索引编号获取一行数据记录。 56 | 57 | `find([(String, Any)])` - 根据查询标准进行检索。查询参数为关键词和对应值构成的字典。一旦匹配这个检查标准,数据记录就会被纳入查询结果。 58 | 59 | `select(whereclause: String, 60 | params: [Any], 61 | orderby: [String])` - 执行一个查询子句,捆绑参数以防范SQL注入式攻击。`orderby`是用于排序的一组关键词,每个字段都允许选择附加`ASC`或`DESC`,分别表示顺序排序和逆序排序。 62 | 63 | 此外,`select`子句可包括: 64 | 65 | * `cursor: StORMCursor` - 可选项 `cursor` 游标对象是一个 [StORMCursor](https://github.com/PerfectlySoft/PerfectDocs/blob/master/guide.zh_CN/StORM-Cursor.md) 66 | 67 | * `columns: [String]` - 可选项 `columns` 是一个字段数组,用于说明查询结果需要返回的各个字段。如果这一个选项不进行设置,那么就会默认返回该数据表的所有字段。 68 | 69 | ### 删除数据记录 70 | 71 | `delete()` - 删除当前对象对应的数据记录行,假设该行数据的主索引编号已经设置。 72 | 73 | `delete(Any)` - 根据主索引编号删除对象及其对应的数据库记录。 74 | 75 | `delete(Int, idname)` - 删除指定字段`idname`取值与`Int`相等的所有数据记录。 76 | 77 | `delete(String, idname)` - 删除指定字段`idname`取值与`String`相等的所有数据记录。 78 | 79 | `delete(UUID, idname)` - 删除指定字段`idname`取值与`UUID`相等的所有数据记录。 80 | 81 | ### 插入数据 82 | 83 | `insert([(String, Any)])` - 将数据记录以字典的表达方式插入数据,每个字典条目的字符串表示字段名称,条目的取值就是目标的字段值。 84 | 85 | `insert(cols: [String], params: [Any])` - 将数据记录以两列不同的数组的表达形式进行插入数据,第一个参数是字段名称构成的数组,第二个参数是具体对应每个字段在本条记录中的取值构成的另外一个数组。 86 | 87 | 88 | ### 更新数据 89 | 90 | `update(data: [(String, Any)], idName: String = "id", idValue: Any)` - 根据主索引编号更新有关数据记录,以字典方式表达数据(每个字典条目对应一个字段名称及该字段的取值),包括可选idName字段名称。 91 | 92 | `update(cols: [String], params: [Any], idName: String, idValue: Any)` - 根据主索引编号更新有关数据记录,以双数组方式表达数据(cols数组是字段名,params数组是对应字段值),包括可选idName字段名称。 93 | 94 | ⚠️译者注⚠️可选的字段名称idName可以是主索引字段,也可以是其他字段,根据程序员选择而定。 95 | 96 | ### SQL 语句 97 | 98 | `sqlExec(String)` - 直接执行SQL查询。 99 | 100 | `sql(String, params: [String])` - 执行SQL查询,附带绑定参数数组。查询完成后会返回一个[SQLiteStmt]查询结果数组。 101 | 102 | `sqlAny(String, params: [String])` 执行SQL查询,附带绑定参数数组。查询完成后会返回一个ID数据列。 103 | 104 | `sqlRows(String, params: [String])` - 执行SQL查询,附带绑定参数数组。查询完成后会返回一个[StORMRow]查询结果数组。 -------------------------------------------------------------------------------- /guide/deployment-Ubuntu.md: -------------------------------------------------------------------------------- 1 | # Producing an Ubuntu Base Image for Swift 4 and Perfect 3 2 | 3 | This guide will share steps that you can use to produce an Ubuntu Linux environment, which is suitable for use with the Swift 4 programming language, and with PerfectlySoft's Perfect 3 application framework. 4 | 5 | ## Running as the Root User 6 | 7 | Begin by installing, deploying, or otherwise utilizing an Ubuntu system. For ease of use, you may use the following command to perform the steps as the **root** user. You'll need to be using an administrative account, and you'll need its password. If you choose to skip this step, you'll need to prefix many of the steps in this guide with the **sudo** command. 8 | 9 | ``` 10 | sudo su 11 | ``` 12 | 13 | ## Running a Screen Session 14 | 15 | If you are connected to the Ubuntu system via **SSH**, you might wish to establish a **screen** session for these steps in case you're disconnected. This section is not mandatory. You can skip to the next section of steps. To establish a **screen** session called "perfect," use: 16 | 17 | ``` 18 | screen -S perfect 19 | ``` 20 | 21 | If **screen** isn't installed, you'll need to install it: 22 | 23 | ``` 24 | apt-get install screen 25 | ``` 26 | 27 | To intentionally disconnect from a **screen** session, you can use **Control-A, D**. To reconnect to the intentionally disconnected **screen** session: 28 | 29 | ``` 30 | screen -r perfect 31 | ``` 32 | 33 | If you were unintentionally disconnected from your **screen** session, you might need to use: 34 | 35 | ``` 36 | screen -D -R perfect 37 | ``` 38 | 39 | ## Install Some Dependencies via APT 40 | 41 | You can install the majority of dependencies for Swift 4 and for Perfect 3 via a free installation script [Perfect Ubuntu](https://github.com/PerfectlySoft/Perfect-Ubuntu.git) 42 | 43 | ## Install MongoDB from Source Code 44 | 45 | Download the MongoDB source code: 46 | 47 | ``` 48 | cd /usr/src/ 49 | wget https://github.com/mongodb/mongo-c-driver/releases/download/1.3.5/mongo-c-driver-1.3.5.tar.gz 50 | ``` 51 | 52 | Decompress the MongoDB source code: 53 | 54 | ``` 55 | gunzip mongo-c-driver-1.3.5.tar.gz 56 | ``` 57 | 58 | Extract the MongoDB source code: 59 | 60 | ``` 61 | tar -xvf mongo-c-driver-1.3.5.tar 62 | ``` 63 | 64 | Remove the MongoDB source code archive: 65 | 66 | ``` 67 | rm mongo-c-driver-1.3.5.tar 68 | ``` 69 | 70 | Configure the MongoDB source code for compilation: 71 | 72 | ``` 73 | cd mongo-c-driver-1.3.5/ 74 | ./configure --enable-sasl=yes 75 | ``` 76 | 77 | Compile MongoDB: 78 | 79 | ``` 80 | make 81 | ``` 82 | 83 | Install MongoDB: 84 | 85 | ``` 86 | make install 87 | ``` 88 | 89 | ## Finished 90 | 91 | Congratulations! You now have an environment suitable for Swift 4 and Perfect 3. 92 | 93 | ## Trying It Out 94 | 95 | Now you can copy the Perfect 3 template project: 96 | 97 | ``` 98 | git clone https://github.com/PerfectlySoft/PerfectTemplate 99 | ``` 100 | 101 | Then build & run: 102 | 103 | ``` 104 | cd PerfectTemplate 105 | swift run 106 | ``` 107 | 108 | You can test this application from a different shell with the `curl` command: 109 | 110 | ``` 111 | curl http://127.0.0.1:8181 112 | ``` 113 | -------------------------------------------------------------------------------- /guide/StORM-MongoDB.md: -------------------------------------------------------------------------------- 1 | # MongoDBStORM 2 | 3 | ## Relevant Examples 4 | 5 | * [MongoDBStORM-Demo](https://github.com/PerfectExamples/MongoDBStORM-Demo) 6 | * [Perfect-Session-MongoDB-Demo](https://github.com/PerfectExamples/Perfect-Session-MongoDB-Demo) 7 | * [Perfect-Turnstile-MongoDB-Demo](https://github.com/PerfectExamples/Perfect-Turnstile-MongoDB-Demo) 8 | 9 | 10 | ## Including in your project 11 | 12 | When including the dependency in your project's Package.swift dependencies, you will have access to all nested dependencies including the database connector. 13 | 14 | ``` swift 15 | .Package(url: "https://github.com/SwiftORM/MongoDB-Storm.git", majorVersion: 3) 16 | ``` 17 | 18 | ## Creating a connection to your database 19 | 20 | In order to connect to your database you will need to specify certain information. 21 | 22 | ``` swift 23 | MongoDBConnection.host = "localhost" 24 | MongoDBConnection.port = 27017 25 | MongoDBConnection.ssl = true 26 | MongoDBConnection.database = "mydb" 27 | 28 | // Authentication credentials. 29 | // Only required if authModeType = .standard 30 | MongoDBConnection.username = "username" 31 | MongoDBConnection.password = "secret" 32 | MongoDBConnection.authdb = "authenticationSource" 33 | 34 | // Authentication mode: 35 | // .none (default), or .standard 36 | MongoDBConnection.authModeType = .none 37 | ``` 38 | 39 | Once your connection information is specified it can be used in the object class to create the connection on demand. 40 | 41 | ``` swift 42 | let obj = User() 43 | ``` 44 | 45 | ## MongoDBStORM supported methods 46 | 47 | ### Connecting 48 | 49 | `MongoDBConnection` - Sets the connection parameters for the MongoDB server access. Host, and database information need to be specified. Port and SSL status need only be specified if different from the default (SSL false, and port 27017). 50 | 51 | ### Creating collections 52 | 53 | The working collection for the object is set by embedding this function in the object, and changing the name to the desired collection name: 54 | 55 | ``` swift 56 | override init() { 57 | super.init() 58 | _collection = "users_demo" 59 | } 60 | ``` 61 | 62 | Note that unlike other StORM implementations, it is not required to invoke a `setup()` function call to ensure the collection is created. MongoDB will create the collection by default if it does not exist at the first data insertion attempt. 63 | 64 | > **NOTE:** The **primary key** is first property defined in the class. 65 | 66 | ### Saving objects 67 | 68 | `save()` - Saves object. If an ID has been defined, save() will perform an update, otherwise a new document is created. 69 | 70 | ### Retrieving data 71 | 72 | `get()` - Retrieves the document. Assumes id has been set in the object. 73 | 74 | `get(String)` - Retrieves a document with a specified id. 75 | 76 | `find([String: Any])` - Performs a find. For example, `try find(["username":"joe"])` will find all documents that have a username equal to "joe". 77 | 78 | Additionally the `find` can include: 79 | 80 | * `cursor: StORMCursor` - The optional `cursor` object is a [StORMCursor](https://github.com/PerfectlySoft/PerfectDocs/blob/master/guide/StORM-Cursor.md) 81 | 82 | ### Deleting objects 83 | 84 | `delete()` - Deletes the current object. Assumes the id property is set. 85 | 86 | 87 | -------------------------------------------------------------------------------- /guide.zh_CN/WebServicesPrimer.md: -------------------------------------------------------------------------------- 1 | # HTTP和Web服务基础 2 | 3 | 大多数已知的Web开发都是在服务器端基于几种主流技术实现的。 4 | 5 | 本文将介绍这些内容,因为这些内容与我们的Perfect(服务器端Swift语言)项目息息相关。此外,本文还将介绍一些应用编程接口(API)开发的基本概念。 6 | 7 | ### 什么是API? 8 | 9 | API是连接两个软件系统之间的桥梁。通常API会接受标准类型作为输入,并在内部转换然后与其它系统工作实现具体的软件操作,或者返回具体的信息。 10 | 11 | 一个API的例子是GitHub网站的API。大多数人可能很熟悉GitHub网站的页面,但是其实GitHub还有一个API。该API允许像[Jira](https://www.atlassian.com/software/jira")这样的应用程序执行任务并发布管理系统,或者像[Bamboo](https://www.atlassian.com/software/bamboo)这样的持续集成系统(CI),能够直接连接到您的代码资源库内以 **为任何单独的系统自身提供更为强大的功能。** 12 | 13 | 这样的API通常都建立在HTTP或HTTPS基础上 14 | 15 | ### HTTP and HTTPS 16 | 17 | “HTTP”是“超文本传输协议”的缩写。这是一组用于在互联网上传输、交换信息和发布多媒体网页的标准。 18 | 19 | “HTTPS”是HTTP的安全加密通信协议。通信的双方通过一个双方都信任的“钥匙”来为通信内容加密解密。安全机制越复杂,其内容被第三方截获、破译和篡改的可能性越低。 20 | 21 | 通常用浏览器访问网站的时候,大部分时间都是通过HTTP完成的;如果您看到浏览器有一个锁的标志,那么应该用的是HTTPS协议。 22 | 23 | 当一个iOS或Android应用程序去访问后台服务器信息时——比如获取天气预报——可能用的是HTTP,但更常见的情况是使用HTTPS。 24 | 25 | ## API的通用形式 26 | 27 | ### 路由 28 | 29 | 一个API可以包含多个“路由”,每个“路由”看起来就像文件系统中的一个目录或文件一样。每个路由节点都可以执行不同的操作。比如一个“查看用户清单”的路由与一个“创建新用户”的路由肯定是不一样的。 30 | 31 | 在一个API中,这些路由往往不是在真实的目录和文件下存在的,而是指向了一些函数。这些“目录结构”暗示了这些函数的逻辑分组。比如: 32 | 33 | ``` 34 | /api/v1/users/list 35 | /api/v1/users/detail 36 | /api/v1/users/create 37 | /api/v1/users/modify 38 | /api/v1/users/delete 39 | 40 | /api/v1/companies/list 41 | /api/v1/companies/detail 42 | /api/v1/companies/create 43 | /api/v1/companies/modify 44 | /api/v1/companies/delete 45 | ``` 46 | 47 | 这个例子描述了一个典型的“CRUD”系统——即Create创建、Read读取、Update更新和Delete删除。两组内容函数的区别貌似仅仅是在`users`用户和`companies`公司着两个实体上,但是其实它们指向的是完全不同的函数。 48 | 49 | 此外,这些“detail”、“modify”和“delete”路由往往会带有一个记录的唯一标示符。比如: 50 | 51 | ``` 52 | /api/v1/users/list 53 | /api/v1/users/detail?id=5d096846-a000-43db-b6c5-a5883135d71d 54 | /api/v1/users/create 55 | /api/v1/users/modify?id=5d096846-a000-43db-b6c5-a5883135d71d 56 | /api/v1/users/delete?id=5d096846-a000-43db-b6c5-a5883135d71d 57 | ``` 58 | 59 | 上述例子说明了传递给服务器的这些路由;id参数关联了一个特定的记录。创建函数create和列表函数list不需要id因为id对于这些路由来说是无关的。 60 | 61 | 除了这些路由之外,发向这些路由的每一个请求都会包括一个HTTP的“动作”。 62 | 63 | ### HTTP动作 64 | 65 | HTTP动作是一个网页浏览器或移动应用端向路由发出请求的额外信息。这些动作能够向API服务器提供数据接收时需要的“上下文”信息。 66 | 67 | 常见HTTP动作包括 68 | 69 | #### GET 70 | GET方法向特定路由发出请求,其中从客户端发向服务器的所有参数包含在URL字符串中。使用GET方法发出的请求只能接收数据,不能由其它作用。使用GET请求来删除数据库的一条记录是可行的,但是不推荐这么做。 71 | 72 | #### POST 73 | POST方法通常用于向数据库内发送创建一条新记录的表单,比如在一个网站上填写好一个表单后提交给服务器。每个表单中都有成对出现的字段名和字段值,便于API服务器读取这些变量信息。 74 | 75 | #### PATCH 76 | PATCH方法整体上来说和POST差不多,但是用于更新记录,而不是新建记录。 77 | 78 | #### PUT 79 | PUT方法主要用于上传文件,一个典型的PUT请求通常会在其消息体内部包含一个即将发给服务器的文件。 80 | 81 | #### DELETE 82 | DELETE请求用于通知API服务器删除特定资源。通常在其URL中会包含一个唯一的标识信息。 83 | 84 | 85 | 86 | ### 使用HTTP动作和路由来简化一个API 87 | 88 | 当综合使用时,每一个HTTP请求的这两个组成部分能够有效降低API的复杂性。 89 | 90 | 查看以下包含了HTTP动作的URL结构,您会发现其内容和形式都要简单多了、也更明确了: 91 | 92 | ``` 93 | GET /api/v1/users 94 | GET /api/v1/users/5d096846-a000-43db-b6c5-a5883135d71d 95 | POST /api/v1/users 96 | PATCH /api/v1/users/5d096846-a000-43db-b6c5-a5883135d71d 97 | DELETE /api/v1/users/5d096846-a000-43db-b6c5-a5883135d71d 98 | ``` 99 | 100 | 上述例子给人第一感觉就是都指向了同一个路由:`/api/v1/users`。但是每一个路由都执行一个不同的操作。类似的,头两个GET路由的区别是第二个GET的后缀带了一个用户id编码作为参数,与之前的例子有所差别。这种命令通常会被一个在路由设置中的“通配符”获取。 101 | 102 | 简单了解上述概念之后,请查看Perfect文档中的[Handling Requests处理请求](handlingRequests.md)有关章节。这些内容有助于理解如何应用路由指向具体的函数和方法,如何将访问从客户端(无论是浏览器还是移动应用)传递给服务器的变量信息。 103 | -------------------------------------------------------------------------------- /guide.zh_CN/OAuth2.md: -------------------------------------------------------------------------------- 1 | # OAuth2 2 | 3 | Perfect Authentication OAuth2 函数库提供了 [OAuth2](https://oauth.net/2/) 编程接口以及对脸谱、谷歌和GitHub的驱动程序支持。 4 | 5 | 下列地址包括了一个详细的展示程序: 6 | [https://github.com/PerfectExamples/Perfect-Authentication-Demo](https://github.com/PerfectExamples/Perfect-Authentication-Demo) 7 | 8 | ## 在项目中使用 9 | 10 | 请在您的项目中修改Package.swift并增加下列依存关系: 11 | 12 | ``` swift 13 | .Package(url: "https://github.com/PerfectlySoft/Perfect-OAuth2.git", majorVersion: 3) 14 | ``` 15 | 16 | 然后导入OAuth2模块: 17 | 18 | ``` swift 19 | import OAuth2 20 | ``` 21 | 22 | ## 配置 23 | 24 | 每个兼容OAuth2协议的厂家都会使用一个应用序号“appid”,也被称为钥匙及其密码(key / secret)。通常这个应用序号都是由厂家OAuth主机分配的,比如脸谱、谷歌和GitHub。除了应用序号之外,您还需要准备“endpointAfterAuth”(完成验证后的接入网址)和“redirectAfterAuth”(完成验证后的重定向跳转网址)用于协议接入。 25 | 26 | 比如,以下是脸谱的应用配置方法: 27 | 28 | ``` swift 29 | FacebookConfig.appid = "yourAppID" 30 | FacebookConfig.secret = "yourSecret" 31 | FacebookConfig.endpointAfterAuth = "http://localhost:8181/auth/response/facebook" 32 | FacebookConfig.redirectAfterAuth = "http://localhost:8181/" 33 | ``` 34 | 35 | 以下是谷歌的应用配置方法: 36 | 37 | ``` swift 38 | GoogleConfig.appid = "yourAppID" 39 | GoogleConfig.secret = "yourSecret" 40 | GoogleConfig.endpointAfterAuth = "http://localhost:8181/auth/response/google" 41 | GoogleConfig.redirectAfterAuth = "http://localhost:8181/" 42 | ``` 43 | 44 | 以下是GitHub的应用配置方法: 45 | 46 | ``` swift 47 | GitHubConfig.appid = "yourAppID" 48 | GitHubConfig.secret = "yourSecret" 49 | GitHubConfig.endpointAfterAuth = "http://localhost:8181/auth/response/github" 50 | GitHubConfig.redirectAfterAuth = "http://localhost:8181/" 51 | ``` 52 | 53 | ## 追加路由 54 | 55 | OAuth2协议依赖与一个身份验证和用户交换系统,简单说来就是用户首先从第三方获取一个身份验证的网络地址,随后在验证完成之后再返回到当前主机的新网址,以确认接受来自第三方的身份验证是否生效。 56 | 57 | 第一组路由用于跳转到OAuth2身份验证提供商,因此具体的网址都是提供商设置好的,用户通常看不到这些链接,因为整个跳转过程都是自动完成的。 58 | 59 | 第二组路由则是由身份验证提供方在完成验证手续后跳转回来的网址,也就是“endpointAfterAuth”配置选项。一旦“authResponse”(授权响应)函数运行结束则用户会自动跳转到“redirectAfterAuth”选项中配置好的新网址。 60 | 61 | ``` swift 62 | var routes: [[String: Any]] = [[String: Any]]() 63 | 64 | routes.append(["method":"get", "uri":"/to/facebook", "handler":Facebook.sendToProvider]) 65 | routes.append(["method":"get", "uri":"/to/github", "handler":GitHub.sendToProvider]) 66 | routes.append(["method":"get", "uri":"/to/google", "handler":Google.sendToProvider]) 67 | 68 | routes.append(["method":"get", "uri":"/auth/response/facebook", "handler":Facebook.authResponse]) 69 | routes.append(["method":"get", "uri":"/auth/response/github", "handler":GitHub.authResponse]) 70 | routes.append(["method":"get", "uri":"/auth/response/google", "handler":Google.authResponse]) 71 | ``` 72 | 73 | ## 第三方认证返回的信息 74 | 75 | 用户在第三方完成身份验证和授权之后,会返回给当前主机一些有关的身份信息。 76 | 77 | 注意,此时您可以采用下列方式获得当前会话的识别编码(Session ID): 78 | 79 | ``` swift 80 | request.session?.token 81 | ``` 82 | 83 | 用户有关信息就存放在这个会话内容中: 84 | 85 | ``` swift 86 | 87 | // 由第三方提供的用户编号 88 | request.session?.userid 89 | 90 | // 第三方类型——如果您的主机允许多个不同的第三方提供身份验证的话, 这个类型会很有用 91 | request.session?.data["loginType"] 92 | 93 | // 会话识别编码 94 | request.session?.data["accessToken"] 95 | 96 | // 第三方提供的用户名字 97 | request.session?.data["firstName"] 98 | 99 | // 第三方提供的用户姓氏 100 | request.session?.data["lastName"] 101 | 102 | // 第三方提供的用户头像 103 | request.session?.data["picture"] 104 | 105 | ``` 106 | 107 | 由上述信息您就可以根据需要自行将用户身份保存到自己的数据库了。 108 | 109 | -------------------------------------------------------------------------------- /guide/fileUploads.md: -------------------------------------------------------------------------------- 1 | # File Uploads 2 | 3 | A special case of [using form data](https://github.com/PerfectlySoft/PerfectDocs/blob/master/guide/formData.md) is handling file uploads. 4 | 5 | There are two main form encoding types: 6 | 7 | * application/x-www-form-urlencoded (the default) 8 | * multipart/form-data 9 | 10 | When you wish to include file upload elements, you must choose multipart/form-data as your form's `enctype` (encoding) type. 11 | 12 | All code used below can be seen in action as a complete example at [https://github.com/iamjono/perfect-file-uploads](https://github.com/iamjono/perfect-file-uploads). 13 | 14 | An example HTML form containing the correct encoding and file input element might be represented like this: 15 | 16 | ``` 17 |
21 | 22 |
23 | 24 |
25 | ``` 26 | 27 | ## Receiving the File on the Server Side 28 | 29 | Because the form is a POST method, we will handle the route with a `method: .post`: 30 | 31 | ``` swift 32 | var routes = Routes() 33 | routes.add( 34 | method: .post, 35 | uri: "/upload", 36 | handler: handler) 37 | server.addRoutes(routes) 38 | ``` 39 | 40 | Once the request has been offloaded to the `handler` we can: 41 | 42 | ``` swift 43 | // Grab the fileUploads array and see what's there 44 | // If this POST was not multi-part, then this array will be empty 45 | 46 | if let uploads = request.postFileUploads, uploads.count > 0 { 47 | // Create an array of dictionaries which will show what was uploaded 48 | var ary = [[String:Any]]() 49 | 50 | for upload in uploads { 51 | ary.append([ 52 | "fieldName": upload.fieldName, 53 | "contentType": upload.contentType, 54 | "fileName": upload.fileName, 55 | "fileSize": upload.fileSize, 56 | "tmpFileName": upload.tmpFileName 57 | ]) 58 | } 59 | values["files"] = ary 60 | values["count"] = ary.count 61 | } 62 | ``` 63 | 64 | As demonstrated above, the file(s) uploaded are represented by the `request.postFileUploads` array, and the various properties such as `fileName`, `fileSize` and `tmpFileName` can be accessed from each array component. 65 | 66 | **Note: The files uploaded are placed in a temporary directory. It is your responsibility to move them into the desired location.** 67 | 68 | So let's create a directory to hold the uploaded files. This directory is outside of the webroot directory for security reasons: 69 | 70 | ``` swift 71 | // create uploads dir to store files 72 | let fileDir = Dir(Dir.workingDir.path + "files") 73 | do { 74 | try fileDir.create() 75 | } catch { 76 | print(error) 77 | } 78 | ``` 79 | 80 | Next, inside the `for upload in uploads` code block, we will create the action for the file to be moved: 81 | 82 | ``` swift 83 | // move file 84 | let thisFile = File(upload.tmpFileName) 85 | do { 86 | let _ = try thisFile.moveTo(path: fileDir.path + upload.fileName, overWrite: true) 87 | } catch { 88 | print(error) 89 | } 90 | ``` 91 | 92 | Now the uploaded files will move to the specified directory with the original filename restored. 93 | 94 | For more information on file system manipulation, see the [Directory Operations](https://github.com/PerfectlySoft/PerfectDocs/blob/master/guide/dir.md) and [File Operations](https://github.com/PerfectlySoft/PerfectDocs/blob/master/guide/file.md) chapters. 95 | -------------------------------------------------------------------------------- /guide/HTTPRequestLogging.md: -------------------------------------------------------------------------------- 1 | # HTTP Request Logging 2 | 3 | To log HTTP requests to a file, use the `Perfect-RequestLogger` module. 4 | 5 | ## Relevant Examples 6 | 7 | * [Perfect-HTTPRequestLogging](https://github.com/PerfectExamples/Perfect-HTTPRequestLogging) 8 | * [Perfect-Session-Memory-Demo](https://github.com/PerfectExamples/Perfect-Session-Memory-Demo) 9 | 10 | ## Usage 11 | 12 | Add the following dependency to the `Package.swift` file: 13 | 14 | ```swift 15 | .Package(url: "https://github.com/PerfectlySoft/Perfect-RequestLogger.git", majorVersion: 3) 16 | ``` 17 | 18 | In each file you wish to implement logging, import the module: 19 | 20 | ``` swift 21 | import PerfectRequestLogger 22 | ``` 23 | 24 | ## When using PerfectHTTP 2.1 or later 25 | 26 | Add to `main.swift` after instantiating your `server`: 27 | 28 | ```swift 29 | // Instantiate a logger 30 | let httplogger = RequestLogger() 31 | 32 | // Configure Server 33 | var confData: [String:[[String:Any]]] = [ 34 | "servers": [ 35 | [ 36 | "name":"localhost", 37 | "port":8181, 38 | "routes":[], 39 | "filters":[ 40 | [ 41 | "type":"response", 42 | "priority":"high", 43 | "name":PerfectHTTPServer.HTTPFilter.contentCompression, 44 | ], 45 | [ 46 | "type":"request", 47 | "priority":"high", 48 | "name":RequestLogger.filterAPIRequest, 49 | ], 50 | [ 51 | "type":"response", 52 | "priority":"low", 53 | "name":RequestLogger.filterAPIResponse, 54 | ] 55 | ] 56 | ] 57 | ] 58 | ] 59 | ``` 60 | The important parts of the configuration spec to add for enabling the Request Logger are: 61 | 62 | ``` swift 63 | [ 64 | "type":"request", 65 | "priority":"high", 66 | "name":RequestLogger.filterAPIRequest, 67 | ], 68 | [ 69 | "type":"response", 70 | "priority":"low", 71 | "name":RequestLogger.filterAPIResponse, 72 | ] 73 | ``` 74 | These request & response filters add the required hooks to mark the beginning and the completion of the HTTP request and response. 75 | 76 | 77 | 78 | ## When using PerfectHTTP 2.0 79 | 80 | Add to `main.swift` after instantiating your `server`: 81 | 82 | ```swift 83 | // Instantiate a logger 84 | let httplogger = RequestLogger() 85 | 86 | // Add the filters 87 | // Request filter at high priority to be executed first 88 | server.setRequestFilters([(httplogger, .high)]) 89 | // Response filter at low priority to be executed last 90 | server.setResponseFilters([(httplogger, .low)]) 91 | ``` 92 | 93 | These request & response filters add the required hooks to mark the beginning and the completion of the HTTP request and response. 94 | 95 | 96 | ## Setting a custom Logfile location 97 | 98 | The default logfile location is `/var/log/perfectLog.log`. To set a custom logfile location, set the `RequestLogFile.location` property: 99 | 100 | ``` swift 101 | RequestLogFile.location = "/var/log/myLog.log" 102 | ``` 103 | 104 | ## Example Log Output 105 | 106 | ``` 107 | [INFO] [62f940aa-f204-43ed-9934-166896eda21c] [servername/WuAyNIIU-1] 2016-10-07 21:49:04 +0000 "GET /one HTTP/1.1" from 127.0.0.1 - 200 64B in 0.000436007976531982s 108 | [INFO] [ec6a9ca5-00b1-4656-9e4c-ddecae8dde02] [servername/WuAyNIIU-2] 2016-10-07 21:49:06 +0000 "GET /two HTTP/1.1" from 127.0.0.1 - 200 64B in 0.000207006931304932s 109 | ``` 110 | 111 | This module expands on earlier work by [David Fleming](https://github.com/dabfleming). 112 | -------------------------------------------------------------------------------- /guide/staticFileContent.md: -------------------------------------------------------------------------------- 1 | # Static File Content 2 | 3 | As seen in the [Routing](https://github.com/PerfectlySoft/PerfectDocs/blob/master/guide/routing.md) chapter, Perfect is capable of complex dynamic routing. It is also capable of serving static content including HTML, images, CSS, and JavaScript. 4 | 5 | Static file content serving is handled through the ```StaticFileHandler``` object. Once a request is given to an instance of this object, it will handle finding the indicated file or returning a 404 if it does not exist. ```StaticFileHandler``` also handles caching through use of the ETag header as well as byte range serving for very large files. 6 | 7 | A ```StaticFileHandler``` object is initialized with a document root path parameter. This root path forms the prefix to which all file paths will be appended. The current HTTPRequest's ```path``` property will be used to indicate the path to the file which should be read and returned. 8 | 9 | ```StaticFileHandler``` can be directly used by creating one in your web handler and calling its ```handleRequest``` method. 10 | 11 | For example, a handler which simply returns the request file might look as follows: 12 | 13 | ```swift 14 | { 15 | request, response in 16 | StaticFileHandler(documentRoot: request.documentRoot) 17 | .handleRequest(request: request, response: response) 18 | } 19 | ``` 20 | 21 | If your server is intended to only server static files it can be launched directly with a document root. 22 | 23 | ```swift 24 | try HTTPServer.launch(.server(name: "localhost", port: 8080, documentRoot: "/path/to/webroot")) 25 | ``` 26 | 27 | The following example establishes a virtual documents path, serving all URIs which begin with "/files" from the physical directory "/var/www/htdocs": 28 | 29 | ``` swift 30 | routes.add(method: .get, uri: "/files/**") { 31 | request, response in 32 | 33 | // get the portion of the request path which was matched by the wildcard 34 | request.path = request.urlVariables[routeTrailingWildcardKey] 35 | 36 | // Initialize the StaticFileHandler with a documentRoot 37 | let handler = StaticFileHandler(documentRoot: "/var/www/htdocs") 38 | 39 | // trigger the handling of the request, 40 | // with our documentRoot and modified path set 41 | handler.handleRequest(request: request, response: response) 42 | } 43 | ``` 44 | 45 | In the route example above, a request to "/files/foo.html" would return the corresponding file "/var/www/htdocs/foo.html". 46 | 47 | ### Content Compression and Performance 48 | 49 | By default, StaticFileHandler will send all file data to the client as quickly as possible. This means it will attempt to use the `sendfile` function which offers better performance when sending file data out over socket connections. There are two cases where this may not be possible or desirable. 50 | 51 | The first case occurs when using an encrypted TLS/SSL connection. In this scenario StaticFileHandler will simply read and send out the file data in chunks. No action is required. StaticFileHandler will detect if the data is being sent over a secured connection and will disable the use of `sendfile`. 52 | 53 | The second case occurs when using content compression. Content compression and usage of `sendfile` do not mix and can give erratic results. To enable content compression with StaticFileHandler, `allowResponseFilters` must be set to true. This is accomplished with an additional parameter passed to the initializer: 54 | 55 | ```swift 56 | StaticFileHandler(documentRoot: "/path/to/root/", allowResponseFilters: true) 57 | ``` 58 | 59 | -------------------------------------------------------------------------------- /guide/StORM-CouchDB.md: -------------------------------------------------------------------------------- 1 | # CouchDBStORM 2 | 3 | ## Relevant Examples 4 | 5 | * [CouchDBStORM-Demo](https://github.com/PerfectExamples/CouchDBStORM-Demo) 6 | * [Perfect-Session-CouchDB-Demo](https://github.com/PerfectExamples/Perfect-Session-CouchDB-Demo) 7 | * [Perfect-Turnstile-CouchDB-Demo](https://github.com/PerfectExamples/Perfect-Turnstile-CouchDB-Demo) 8 | 9 | 10 | ## Including in your project 11 | 12 | When including the dependency in your project's Package.swift dependencies, you will have access to all nested dependencies including the database connector. 13 | 14 | ``` swift 15 | .Package(url: "https://github.com/SwiftORM/CouchDB-Storm.git", majorVersion: 3) 16 | ``` 17 | 18 | 19 | ## Creating a connection to your database 20 | 21 | In order to connect to your database you will need to specify certain information. 22 | 23 | ``` swift 24 | CouchDBConnection.host = "localhost" 25 | CouchDBConnection.username = "username" 26 | CouchDBConnection.password = "secret" 27 | CouchDBConnection.port = 5984 28 | CouchDBConnection.ssl = true 29 | ``` 30 | 31 | Once your connection information is specified it can be used in the object class to create the connection on demand. 32 | 33 | ``` swift 34 | let obj = User() 35 | ``` 36 | 37 | ## CouchDBStORM supported methods 38 | 39 | ### Connecting 40 | 41 | `CouchDBConnection` - Sets the connection parameters for the CouchDB server access. Host, username and password information need to be specified. Port and SSL status need only be specified if different from the default (SSL false, and port 5984). 42 | 43 | ### Creating databases 44 | 45 | The working database for the object is set by embedding this function in the object, and changing the name to the desired database name: 46 | 47 | ``` swift 48 | override open func database() -> String { 49 | return "mydbname" 50 | } 51 | ``` 52 | 53 | `setup()` - Creates the database. 54 | 55 | > **NOTE:** The **primary key** is first property defined in the class. 56 | 57 | ### Saving objects 58 | 59 | `save(rev: String = "")` - Saves object. If an ID has been defined, save() will perform an update, otherwise a new document is created. Takes an optional "rev" parameter which is the document revision to be used. If empty the object's stored _rev property is used. 60 | 61 | `save(rev: String = "") {id in ... }` - Saves object. If an ID has been defined, save() will perform an update, otherwise a new document is created. Takes an optional "rev" parameter which is the document revision to be used. If empty the object's stored _rev property is used. The closure returns the new id if created. 62 | 63 | `create()` - Forces the creation of a new database object. The revision property is also set once the save has been completed. 64 | 65 | ### Retrieving data 66 | 67 | `get()` - Retrieves the document. Assumes id has been set in the object. 68 | 69 | `get(String)` - Retrieves a document with a specified id. 70 | 71 | `find([String: Any])` - Performs a find using the selector syntax. For example, `try find(["username":"joe"])` will find all documents that have a username equal to "joe". Full selector syntax can be found at [http://docs.couchdb.org/en/2.0.0/api/database/find.html#find-selectors](http://docs.couchdb.org/en/2.0.0/api/database/find.html#find-selectors) 72 | 73 | Additionally the `find` can include: 74 | 75 | * `cursor: StORMCursor` - The optional `cursor` object is a [StORMCursor](https://github.com/PerfectlySoft/PerfectDocs/blob/master/guide/StORM-Cursor.md) 76 | 77 | ### Deleting objects 78 | 79 | `delete()` - Deletes the current object. Assumes the id and _rev properties are set. 80 | 81 | 82 | -------------------------------------------------------------------------------- /guide/sysProcess.md: -------------------------------------------------------------------------------- 1 | # SysProcess 2 | 3 | Perfect provides the ability to execute local processes or shell commands through the `SysProcess` type. This type allows local processes to be launched with an array of parameters and shell variables. Some processes will execute and return a result immediately. Other processes can be left open for interactive read/write operations. 4 | 5 | ## Relevant Examples 6 | 7 | * [Perfect-System](https://github.com/PerfectExamples/Perfect-System) 8 | 9 | ## Setup 10 | 11 | Add the "Perfect" project as a dependency in your Package.swift file: 12 | 13 | ``` swift 14 | .Package( 15 | url: "https://github.com/PerfectlySoft/PerfectLib.git", 16 | majorVersion: 3 17 | ) 18 | ``` 19 | In your file where you wish to use SysProcess, import the PerfectLib and add either SwiftGlibc or Darwin: 20 | 21 | ``` swift 22 | import PerfectLib 23 | 24 | #if os(Linux) 25 | import SwiftGlibc 26 | #else 27 | import Darwin 28 | #endif 29 | ``` 30 | 31 | ### Executing a SysProcess Command 32 | 33 | The following function `runProc` accepts a command, an array of arguments, and optionally outputs the response from the command. 34 | 35 | ``` swift 36 | func runProc(cmd: String, args: [String], read: Bool = false) throws -> String? { 37 | let envs = [("PATH", "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin")] 38 | let proc = try SysProcess(cmd, args: args, env: envs) 39 | var ret: String? 40 | if read { 41 | var ary = [UInt8]() 42 | while true { 43 | do { 44 | guard let s = try proc.stdout?.readSomeBytes(count: 1024) where s.count > 0 else { 45 | break 46 | } 47 | ary.append(contentsOf: s) 48 | } catch PerfectLib.PerfectError.fileError(let code, _) { 49 | if code != EINTR { 50 | break 51 | } 52 | } 53 | } 54 | ret = UTF8Encoding.encode(bytes: ary) 55 | } 56 | let res = try proc.wait(hang: true) 57 | if res != 0 { 58 | let s = try proc.stderr?.readString() 59 | throw PerfectError.systemError(Int32(res), s!) 60 | } 61 | return ret 62 | } 63 | 64 | let output = try runProc(cmd: "ls", args: ["-la"], read: true) 65 | print(output) 66 | ``` 67 | 68 | Note that the `SysProcess` command is executed in this example with a hardcoded environment variable. 69 | 70 | ### SysProcess Members 71 | 72 | #### stdin 73 | `stdin` is the standard input file stream. 74 | 75 | #### stdout 76 | `stdout` is the standard output file stream. 77 | 78 | #### stderr 79 | `stderr` is the standard error file stream. 80 | 81 | #### pid 82 | `pid` is the process identifier. 83 | 84 | ### SysProcess Methods 85 | 86 | #### isOpen 87 | 88 | Returns true if the process was opened and was running at some point. 89 | 90 | Note that the process may not be currently running. Use `wait(false)` to check if the process is currently running. 91 | 92 | ``` swift 93 | myProcess.isOpen() 94 | ``` 95 | 96 | #### close 97 | 98 | `close` terminates the process and cleans up. 99 | 100 | ``` swift 101 | myProcess.close() 102 | ``` 103 | 104 | #### detach 105 | 106 | Detach from the process such that it will not be manually terminated when this object is uninitialized. 107 | 108 | ``` swift 109 | myProcess.detach() 110 | ``` 111 | 112 | #### wait 113 | 114 | Determine if the process has completed running and retrieve its result code. 115 | 116 | ``` swift 117 | myProcess.wait(hang: ) 118 | ``` 119 | 120 | #### kill 121 | 122 | Terminate the process and return its result code. 123 | 124 | ``` swift 125 | myProcess.kill(signal: ) 126 | ``` 127 | Response is an `Int32` result code. 128 | -------------------------------------------------------------------------------- /guide.zh_CN/StORM-MySQL.md: -------------------------------------------------------------------------------- 1 | # MySQLStORM 2 | 3 | ### 在工程中引用 4 | 5 | 请首先在您的 Package.swift 文件中更新依存关系,以确保必要的函数库能够下载到编译环境用于数据库连接: 6 | 7 | ``` swift 8 | .Package(url: "https://github.com/SwiftORM/MySQL-StORM", majorVersion: 3) 9 | ``` 10 | 11 | ### 关于防范SQL注入式攻击 12 | 13 | StORM 并无法确保您的数据被黑客通过参数化捆绑方式进行SQL注入式攻击。 14 | 15 | 但是在某些函数中,您是完全可以验证SQL查询的语句合法性的,因此,请务必在使用SQL时注意检查外来数据内容。 16 | 17 | ## 创建数据库连接 18 | 19 | 为了创建数据库连接,请自行更新数据源配置: 20 | 21 | ``` swift 22 | MySQLConnector.host = "localhost" 23 | MySQLConnector.username = "username" 24 | MySQLConnector.password = "secret" 25 | MySQLConnector.database = "yourdatabase" 26 | MySQLConnector.port = 3306 27 | ``` 28 | 29 | 一旦连接参数配置完成,则数据库会在需要时自动连接。 30 | 31 | 32 | ``` swift 33 | let obj = User() 34 | ``` 35 | 36 | ## MySQLStORM 支持的函数方法 37 | 38 | ### 数据库连接 39 | 40 | `MySQLConnector ` - 设置MySQL 服务器连接参数,包括主机名,端口,用户名和密码,以及默认数据库等等。 41 | 42 | ### 创建数据表 43 | 44 | `setup()` - 通过检查对象直接创建数据表。所有的数据字段会根据该对象的属性自动创建。如果属性以下划线或者 `internal_`命名,则该属性不会作为数据字段进行创建,会被忽略。 45 | 46 | > **⚠️注意⚠️** 其中数据库的 **主索引键** 是该类中的第一个字段。 47 | 48 | #### 为创建表格单独设置SQL语句 49 | 50 | `setup(String)` - 手工设置创建数据表格的SQL语句,覆盖自动创建数据表的方法。 51 | 52 | ### 将程序中的对象数据保存到数据库 53 | 54 | `save()` - 保存对象。如果当前没有设置主索引键的值,那么就会向数据库内插入一条新的数据。否则如果主索引键已经设置了具体的数值,则会进行一个更新操作。 55 | 56 | `save {id in ... }` - 保存对象。如果当前没有设置主索引键的值,那么就会向数据库内插入一条新的数据。否则如果主索引键已经设置了具体的数值,则会进行一个更新操作。闭包用于如果创建成功的话,返回一个创建完成的数据行id。 57 | 58 | `create()` - 显式要求以插入新数据的方式保存数据,无论主索引键是否已经设置了内容。 59 | 60 | ### 获取数据 61 | 62 | `findAll()` - 获取全部数据行,仅受限于数据游标(9,999,999行) 63 | 64 | `get()` - 假设主索引键已经设置的情况下,获取一行数据记录。 65 | 66 | `get(Any)` - 根据主索引编号获取一行数据记录。 67 | 68 | `find([(String, Any)])` - 根据查询标准进行检索。查询参数为关键词和对应值构成的字典。一旦匹配这个检查标准,数据记录就会被纳入查询结果。 69 | 70 | `select(whereclause: String, 71 | params: [Any], 72 | orderby: [String])` - 执行一个查询子句,捆绑参数以防范SQL注入式攻击。`orderby`是用于排序的一组关键词,每个字段都允许选择附加`ASC`或`DESC`,分别表示顺序排序和逆序排序。 73 | 74 | 此外,`select`子句可包括: 75 | 76 | * `cursor: StORMCursor` - 可选项 `cursor` 游标对象是一个 [StORMCursor](https://github.com/PerfectlySoft/PerfectDocs/blob/master/guide.zh_CN/StORM-Cursor.md) 77 | 78 | * `columns: [String]` - 可选项 `columns` 是一个字段数组,用于说明查询结果需要返回的各个字段。如果这一个选项不进行设置,那么就会默认返回该数据表的所有字段。 79 | 80 | ### 删除数据记录 81 | 82 | `delete()` - 删除当前对象对应的数据记录行,假设该行数据的主索引编号已经设置。 83 | 84 | `delete(Any)` - 根据主索引编号删除对象及其对应的数据库记录。 85 | 86 | `delete(Int, idname)` - 删除指定字段`idname`取值与`Int`相等的所有数据记录。 87 | 88 | `delete(String, idname)` - 删除指定字段`idname`取值与`String`相等的所有数据记录。 89 | 90 | `delete(UUID, idname)` - 删除指定字段`idname`取值与`UUID`相等的所有数据记录。 91 | 92 | ### 插入数据 93 | 94 | `insert([(String, Any)])` - 将数据记录以字典的表达方式插入数据,每个字典条目的字符串表示字段名称,条目的取值就是目标的字段值。 95 | 96 | `insert(cols: [String], params: [Any])` - 将数据记录以两列不同的数组的表达形式进行插入数据,第一个参数是字段名称构成的数组,第二个参数是具体对应每个字段在本条记录中的取值构成的另外一个数组。 97 | 98 | `insert(cols: [String], params: [Any], idcolumn: String)` - 同上,不过要求返回idcolumn 字段值作为查询结果。 99 | 100 | ### 更新数据 101 | 102 | `update(data: [(String, Any)], idName: String = "id", idValue: Any)` - 根据主索引编号更新有关数据记录,以字典方式表达数据(每个字典条目对应一个字段名称及该字段的取值),包括可选idName字段名称。 103 | 104 | `update(cols: [String], params: [Any], idName: String, idValue: Any)` - 根据主索引编号更新有关数据记录,以双数组方式表达数据(cols数组是字段名,params数组是对应字段值),包括可选idName字段名称。 105 | 106 | ⚠️译者注⚠️可选的字段名称idName可以是主索引字段,也可以是其他字段,根据程序员选择而定。 107 | 108 | ### Upserts 109 | 110 | `upsert(cols: [String], params: [Any], conflictkeys: [String])` - 插入指定数据;如果发现有重复记录(用`conflictkeys`标出的字段列为不可重复)的情况下,执行更新(覆盖)。 111 | 112 | ### SQL 语句 113 | 114 | `sqlExec(String)` - 直接执行SQL查询。 115 | 116 | `sql(String, params: [String])` - 执行SQL查询,附带绑定参数数组。查询完成后会返回一个[PGResult]查询结果数组。 117 | 118 | `sqlAny(String, params: [String])` 执行SQL查询,附带绑定参数数组。查询完成后会返回一个[StORMRow]查询结果数组。 119 | 120 | `sqlRows(String, params: [String])` - 执行SQL查询,附带绑定参数数组。查询完成后会返回一个[StORMRow]查询结果数组。 121 | -------------------------------------------------------------------------------- /guide/deployment-Docker.md: -------------------------------------------------------------------------------- 1 | # Docker Deployment 2 | 3 | This guide contains steps which will allow you to derive your Docker image for your Swift + Perfect application. This particular guide is for deriving from the *perfect2-swift20160620-ubuntu1510* image, which contains: 4 | 5 | * Ubuntu 15.10 6 | * Swift development snapshot 2016-06-20 (prior to Swift 4 being released) 7 | * Perfect 3 8 | 9 | You'll need Docker to be available on your system: 10 | 11 | * If you have an Ubuntu system, you can use: 12 | `apt-get install docker` 13 | 14 | * If you have a macOS system, you can consult these instructions: 15 | [https://docs.docker.com/engine/installation/mac/](https://docs.docker.com/engine/installation/mac/) 16 | 17 | * If you have a Microsoft Windows system, you can consult these instructions: 18 | [https://docs.docker.com/engine/installation/windows/](https://docs.docker.com/engine/installation/windows/) 19 | 20 | * For other Linux systems, you can consult these instructions: 21 | [https://docs.docker.com/engine/installation/](https://docs.docker.com/engine/installation/) 22 | 23 | Once you have Docker available, you can fetch the perfect2-swift20160620-ubuntu1510 repository with the following command: 24 | 25 | ``` 26 | docker pull perfectlysoft/perfect2-swift20160620-ubuntu1510 27 | ``` 28 | 29 | Now we'll derive from that Docker image and fetch your application into the derived image. For the sake of demonstration, we'll pretend that your application is available via GitHub, at: 30 | 31 | ``` 32 | https://github.com/PerfectlySoft/PerfectTemplate 33 | ``` 34 | 35 | We need to start a shell inside the Docker container: 36 | 37 | ``` 38 | docker run -t -i perfectlysoft/perfect2-swift20160620-ubuntu1510 bash 39 | ``` 40 | 41 | Now our shell is running inside the Docker container. Venture into the `/usr/src/` directory: 42 | 43 | ``` 44 | cd /usr/src/ 45 | ``` 46 | 47 | Clone your application's Git repository: 48 | 49 | ``` 50 | git clone https://github.com/PerfectlySoft/PerfectTemplate 51 | ``` 52 | 53 | Venture inside your application's repository: 54 | 55 | ``` 56 | cd PerfectTemplate/ 57 | ``` 58 | 59 | Your Docker container will require internet access. Build your application: 60 | 61 | ``` 62 | swift build 63 | ``` 64 | 65 | Once built, your application will be available inside the `.build/debug/` directory. You can copy it to somewhere more convenient, such as the `/root/` directory: 66 | 67 | ``` 68 | cp .build/debug/PerfectTemplate /root/ 69 | ``` 70 | 71 | Now check and make a note of the hostname of your container: 72 | 73 | ``` 74 | hostname 75 | ``` 76 | 77 | For example, the hostname might be 2babae7df6fe. Now exit the container: 78 | 79 | ``` 80 | exit 81 | ``` 82 | 83 | Capture your Swift + Perfect application with its image, using the hostname you'd made a note of: 84 | 85 | ``` 86 | docker commit -m "My application" -a "My Name" 2babae7df6fe myapp 87 | ``` 88 | 89 | (You can use your own application-name instead of "My application", your own name or company-name instead of "My Name", or a Docker image-name instead of "myapp".) 90 | 91 | Now you can confirm that your image has been created: 92 | 93 | ``` 94 | docker images 95 | ``` 96 | 97 | Deploy your application like this: 98 | 99 | ``` 100 | docker run -d -p 0.0.0.0:8080:8181 myapp /root/PerfectTemplate 101 | ``` 102 | 103 | What the above command does is to have TCP port `8080` on your host system (`0.0.0.0`) redirect to TCP port 8181 in the Docker container (based on your myapp image) that's running your application (`/root/PerfectTemplate`). In this example, the PerfectTemplate program listens on TCP port `8181`. You can test your application with the cURL command: 104 | 105 | ``` 106 | curl http://127.0.0.1:8080 107 | ``` 108 | 109 | -------------------------------------------------------------------------------- /guide.zh_CN/StORM-PostgreSQL.md: -------------------------------------------------------------------------------- 1 | # PostgresStORM 2 | 3 | ### 在工程中引用 4 | 5 | 请首先在您的 Package.swift 文件中更新依存关系,以确保必要的函数库能够下载到编译环境用于数据库连接: 6 | 7 | ``` swift 8 | .Package(url: "https://github.com/SwiftORM/Postgres-Storm.git", majorVersion: 3) 9 | ``` 10 | ### 关于防范SQL注入式攻击 11 | 12 | StORM 并无法确保您的数据被黑客通过参数化捆绑方式进行SQL注入式攻击。 13 | 14 | 但是在某些函数中,您是完全可以验证SQL查询的语句合法性的,因此,请务必在使用SQL时注意检查外来数据内容。 15 | 16 | ## 创建数据库连接 17 | 18 | 为了创建数据库连接,请自行更新数据源配置: 19 | 20 | ``` swift 21 | PostgresConnector.host = "localhost" 22 | PostgresConnector.username = "username" 23 | PostgresConnector.password = "secret" 24 | PostgresConnector.database = "yourdatabase" 25 | PostgresConnector.port = 5432 26 | ``` 27 | 28 | 一旦连接参数配置完成,则数据库会在需要时自动连接。 29 | 30 | 31 | ``` swift 32 | let obj = User() 33 | ``` 34 | 35 | 36 | ## PostgresStORM 支持的函数方法 37 | 38 | ### 数据库连接 39 | 40 | `PostgresConnector ` - 设置MySQL 服务器连接参数,包括主机名,端口,用户名和密码,以及默认数据库等等。 41 | 42 | ### 创建数据表 43 | 44 | `setup()` - 通过检查对象直接创建数据表。所有的数据字段会根据该对象的属性自动创建。如果属性以下划线或者 `internal_`命名,则该属性不会作为数据字段进行创建,会被忽略。 45 | 46 | > **⚠️注意⚠️** 其中数据库的 **主索引键** 是该类中的第一个字段。 47 | 48 | #### 为创建表格单独设置SQL语句 49 | 50 | `setup(String)` - 手工设置创建数据表格的SQL语句,覆盖自动创建数据表的方法。 51 | 52 | ### 将程序中的对象数据保存到数据库 53 | 54 | `save()` - 保存对象。如果当前没有设置主索引键的值,那么就会向数据库内插入一条新的数据。否则如果主索引键已经设置了具体的数值,则会进行一个更新操作。 55 | 56 | `save {id in ... }` - 保存对象。如果当前没有设置主索引键的值,那么就会向数据库内插入一条新的数据。否则如果主索引键已经设置了具体的数值,则会进行一个更新操作。闭包用于如果创建成功的话,返回一个创建完成的数据行id。 57 | 58 | `create()` - 显式要求以插入新数据的方式保存数据,无论主索引键是否已经设置了内容。 59 | 60 | ### 获取数据 61 | 62 | `findAll()` - 获取全部数据行,仅受限于数据游标(9,999,999行) 63 | 64 | `get()` - 假设主索引键已经设置的情况下,获取一行数据记录。 65 | 66 | `get(Any)` - 根据主索引编号获取一行数据记录。 67 | 68 | `find([(String, Any)])` - 根据查询标准进行检索。查询参数为关键词和对应值构成的字典。一旦匹配这个检查标准,数据记录就会被纳入查询结果。 69 | 70 | `select(whereclause: String, 71 | params: [Any], 72 | orderby: [String])` - 执行一个查询子句,捆绑参数以防范SQL注入式攻击。`orderby`是用于排序的一组关键词,每个字段都允许选择附加`ASC`或`DESC`,分别表示顺序排序和逆序排序。 73 | 74 | 此外,`select`子句可包括: 75 | 76 | * `cursor: StORMCursor` - 可选项 `cursor` 游标对象是一个 [StORMCursor](https://github.com/PerfectlySoft/PerfectDocs/blob/master/guide.zh_CN/StORM-Cursor.md) 77 | 78 | * `columns: [String]` - 可选项 `columns` 是一个字段数组,用于说明查询结果需要返回的各个字段。如果这一个选项不进行设置,那么就会默认返回该数据表的所有字段。 79 | 80 | ### 删除数据记录 81 | 82 | `delete()` - 删除当前对象对应的数据记录行,假设该行数据的主索引编号已经设置。 83 | 84 | `delete(Any)` - 根据主索引编号删除对象及其对应的数据库记录。 85 | 86 | `delete(Int, idname)` - 删除指定字段`idname`取值与`Int`相等的所有数据记录。 87 | 88 | `delete(String, idname)` - 删除指定字段`idname`取值与`String`相等的所有数据记录。 89 | 90 | `delete(UUID, idname)` - 删除指定字段`idname`取值与`UUID`相等的所有数据记录。 91 | 92 | ### 插入数据 93 | 94 | `insert([(String, Any)])` - 将数据记录以字典的表达方式插入数据,每个字典条目的字符串表示字段名称,条目的取值就是目标的字段值。 95 | 96 | `insert(cols: [String], params: [Any])` - 将数据记录以两列不同的数组的表达形式进行插入数据,第一个参数是字段名称构成的数组,第二个参数是具体对应每个字段在本条记录中的取值构成的另外一个数组。 97 | 98 | `insert(cols: [String], params: [Any], idcolumn: String)` - 同上,不过要求返回idcolumn 字段值作为查询结果。 99 | 100 | ### 更新数据 101 | 102 | `update(data: [(String, Any)], idName: String = "id", idValue: Any)` - 根据主索引编号更新有关数据记录,以字典方式表达数据(每个字典条目对应一个字段名称及该字段的取值),包括可选idName字段名称。 103 | 104 | `update(cols: [String], params: [Any], idName: String, idValue: Any)` - 根据主索引编号更新有关数据记录,以双数组方式表达数据(cols数组是字段名,params数组是对应字段值),包括可选idName字段名称。 105 | 106 | ⚠️译者注⚠️可选的字段名称idName可以是主索引字段,也可以是其他字段,根据程序员选择而定。 107 | 108 | ### Upserts 109 | 110 | `upsert(cols: [String], params: [Any], conflictkeys: [String])` - 插入指定数据;如果发现有重复记录(用`conflictkeys`标出的字段列为不可重复)的情况下,执行更新(覆盖)。 111 | 112 | ### SQL 语句 113 | 114 | `sqlExec(String)` - 直接执行SQL查询。 115 | 116 | `sql(String, params: [String])` - 执行SQL查询,附带绑定参数数组。查询完成后会返回一个[PGResult]查询结果数组。 117 | 118 | `sqlAny(String, params: [String])` 执行SQL查询,附带绑定参数数组。查询完成后会返回一个[StORMRow]查询结果数组。 119 | 120 | `sqlRows(String, params: [String])` - 执行SQL查询,附带绑定参数数组。查询完成后会返回一个[StORMRow]查询结果数组。 121 | -------------------------------------------------------------------------------- /guide/dir.md: -------------------------------------------------------------------------------- 1 | # Directory Operations 2 | 3 | Perfect brings file system operations into your sever-side Swift environments to control how data is stored and retrieved in an accessible way. 4 | 5 | ## Relevant Examples 6 | 7 | * [Perfect-Directory-Lister](https://github.com/PerfectExamples/Perfect-Directory-Lister) 8 | * [Perfect-FileHandling](https://github.com/PerfectExamples/Perfect-FileHandling) 9 | 10 | ## Usage 11 | 12 | First, ensure the `PerfectLib` is imported in your Swift file: 13 | 14 | ``` swift 15 | import PerfectLib 16 | ``` 17 | You are now able to use the `Dir` object to query and manipulate the file system. 18 | 19 | ### Setting Up a Directory Object Reference 20 | 21 | Specify the absolute or relative path to the directory: 22 | 23 | ``` swift 24 | let thisDir = Dir("/path/to/directory/") 25 | ``` 26 | 27 | ### Checking If a Directory Exists 28 | 29 | Use the `exists` method to return a Boolean value. 30 | 31 | ``` swift 32 | let thisDir = Dir("/path/to/directory/") 33 | thisDir.exists 34 | ``` 35 | 36 | ### Returning the Current Directory Object's Name 37 | 38 | `name` returns the name of the object's directory. Note that this is different from the "path". 39 | 40 | ``` swift 41 | thisDir.name 42 | ``` 43 | 44 | ### Returning the Parent Directory 45 | 46 | `parentDir` returns a `Dir` object representing the current directory object's parent. Returns nil if there is no parent. 47 | 48 | ``` swift 49 | let thisDir = Dir("/path/to/directory/") 50 | let parent = thisDir.parentDir 51 | ``` 52 | 53 | ### Revealing the Directory Path 54 | 55 | `path` returns the path to the current directory. 56 | 57 | ``` swift 58 | let thisDir = Dir("/path/to/directory/") 59 | let path = thisDir.path 60 | ``` 61 | 62 | ### Returning the Directory's UNIX Permissions 63 | 64 | `perms` returns the UNIX style permissions for the directory as a `PermissionMode` object. 65 | 66 | ``` swift 67 | thisDir.perms 68 | ``` 69 | 70 | For example: 71 | 72 | ``` swift 73 | print(thisDir.perms) 74 | >> PermissionMode(rawValue: 29092) 75 | ``` 76 | 77 | ### Creating a Directory 78 | 79 | `create` creates the directory using the provided permissions. All directories along the path will be created if needed. 80 | 81 | The following will create a new directory with the default permissions (Owner: read-write-execute, Group and Everyone: read-execute. 82 | 83 | ``` swift 84 | let newDir = Dir("/path/to/directory/newDirectory") 85 | try newDir.create() 86 | ``` 87 | 88 | To create a directory with specific permissions, specify the `perms` parameter: 89 | 90 | ``` swift 91 | let newDir = Dir("/path/to/directory/newDirectory") 92 | try newDir.create(perms: [.rwxUser, .rxGroup, .rxOther]) 93 | ``` 94 | 95 | The method throws `PerfectError.FileError` if an error creating the directory was encountered. 96 | 97 | 98 | ### Deleting a Directory 99 | 100 | Deleting a directory from the file system: 101 | 102 | ``` swift 103 | let newDir = Dir("/path/to/directory/newDirectory") 104 | try newDir.delete() 105 | ``` 106 | 107 | The method throws `PerfectError.FileError` if an error deleting the directory was encountered. 108 | 109 | ### Working Directories 110 | 111 | ### Set the Working Directory to the Location of the Current Object 112 | 113 | Use `setAsWorkingDir` to set the current working directory to the location of the object's path. 114 | 115 | ``` swift 116 | let thisDir = Dir("/path/to/directory/") 117 | try thisDir.setAsWorkingDir() 118 | ``` 119 | 120 | ### Return the Current Working Directory 121 | 122 | Returns a new object containing the current working directory. 123 | 124 | ``` swift 125 | let workingDir = Dir.workingDir 126 | ``` 127 | 128 | ### Reading the Directory Structure 129 | 130 | `forEachEntry` enumerates the contents of the directory, passing the name of each contained element to the provided callback. 131 | 132 | ``` swift 133 | try thisDir.forEachEntry(closure: { 134 | n in 135 | print(n) 136 | }) 137 | ``` 138 | -------------------------------------------------------------------------------- /guide.zh_CN/toc_dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "contents": [ 3 | { 4 | "name": "简介", 5 | "doc": "introduction" 6 | }, 7 | { 8 | "name": "快速上手", 9 | "doc": "gettingStarted" 10 | }, 11 | { 12 | "name": "HTTP和Web服务基础", 13 | "doc": "WebServicesPrimer" 14 | }, 15 | { 16 | "name": "代码资源库结构", 17 | "doc": "repositoryLayout" 18 | }, 19 | { 20 | "name": "用SPM软件包管理器编译项目", 21 | "doc": "buildingWithSPM" 22 | }, 23 | { 24 | "name": "处理HTTP请求", 25 | "doc": "handlingRequests", 26 | "contents": [ 27 | { 28 | "name": "HTTP路由", 29 | "doc": "routing" 30 | }, 31 | { 32 | "name": "HTTPRequest请求对象", 33 | "doc": "HTTPRequest", 34 | "contents" : [ 35 | { 36 | "name": "使用表单", 37 | "doc": "formData" 38 | }, 39 | { 40 | "name": "文件上传", 41 | "doc": "fileUploads" 42 | } 43 | 44 | ] 45 | }, 46 | { 47 | "name": "HTTPResponse响应对象", 48 | "doc": "HTTPResponse" 49 | }, 50 | { 51 | "name": "HTTP请求与响应过滤器", 52 | "doc": "filters" 53 | }, 54 | { 55 | "name": "JSON数据转换", 56 | "doc": "JSON" 57 | }, 58 | { 59 | "name": "静态文件", 60 | "doc": "staticFileContent" 61 | }, 62 | { 63 | "name": "Mustache页面模板", 64 | "doc": "mustache" 65 | } 66 | ] 67 | }, 68 | { 69 | "name": "基本工具", 70 | "doc": "utilities", 71 | "contents": [ 72 | { 73 | "name": "字节流转换", 74 | "doc": "bytes" 75 | }, 76 | { 77 | "name": "文件操作", 78 | "doc": "file" 79 | }, 80 | { 81 | "name": "目录与路径", 82 | "doc": "dir" 83 | }, 84 | { 85 | "name": "线程", 86 | "doc": "thread" 87 | }, 88 | { 89 | "name": "网络", 90 | "doc": "net" 91 | }, 92 | { 93 | "name": "UUID唯一标识符", 94 | "doc": "UUID" 95 | }, 96 | { 97 | "name": "SysProcess系统进程", 98 | "doc": "sysProcess" 99 | }, 100 | { 101 | "name": "日志", 102 | "doc": "log" 103 | }, 104 | { 105 | "name": "CURL联网传输", 106 | "doc": "cURL" 107 | } 108 | 109 | ] 110 | }, 111 | { 112 | "name": "数据库连接器", 113 | "doc": "databaseConnectors", 114 | "contents": [ 115 | { 116 | "name": "SQLite", 117 | "doc": "SQLite" 118 | }, 119 | { 120 | "name": "MySQL", 121 | "doc": "MySQL" 122 | }, 123 | { 124 | "name": "PostgreSQL", 125 | "doc": "PostgreSQL" 126 | }, 127 | { 128 | "name": "MongoDB", 129 | "doc": "MongoDB", 130 | "contents": [ 131 | { 132 | "name": "MongoDB 数据库", 133 | "doc": "MongoDB-Database" 134 | }, 135 | { 136 | "name": "MongoDB 集合", 137 | "doc": "MongoDB-Collections" 138 | }, 139 | { 140 | "name": "MongoDB 客户端", 141 | "doc": "MongoDB-Client" 142 | }, 143 | { 144 | "name": "BSON 数据转换", 145 | "doc": "MongoDB-BSON" 146 | } 147 | 148 | ] 149 | }, 150 | { 151 | "name": "Redis", 152 | "doc": "Redis" 153 | } 154 | 155 | ] 156 | }, 157 | { 158 | "name": "WebSockets", 159 | "doc": "webSockets" }, 160 | { 161 | "name": "iOS 消息与通知", 162 | "doc": "iOSNotifications" }, 163 | { 164 | "name": "发行与部署", 165 | "doc": "deployment", 166 | "contents": [ 167 | { 168 | "name": "Docker", 169 | "doc": "deployment-Docker" 170 | }, 171 | { 172 | "name": "Heroku", 173 | "doc": "deployment-Heroku" 174 | }, 175 | { 176 | "name": "Azure", 177 | "doc": "deployment-Azure" 178 | }, 179 | { 180 | "name": "AWS", 181 | "doc": "deployment-AWS" 182 | }, 183 | { 184 | "name": "Linode", 185 | "doc": "deployment-Linode" 186 | }, 187 | { 188 | "name": "Digital Ocean", 189 | "doc": "deployment-DigitalOcean" 190 | } 191 | 192 | ] 193 | } 194 | ] 195 | } 196 | -------------------------------------------------------------------------------- /guide/csrf.md: -------------------------------------------------------------------------------- 1 | ## CSRF (Cross Site Request Forgery) Security 2 | 3 | Cross-Site Request Forgery (CSRF) is an attack that forces an end user to execute unwanted actions on a web application in which they're currently authenticated. **CSRF attacks specifically target state-changing requests, not theft of data, since the attacker has no way to see the response to the forged request.** With a little help of social engineering (such as sending a link via email or chat), an attacker may trick the users of a web application into executing actions of the attacker's choosing. If the victim is a normal user, a successful CSRF attack can force the user to perform state changing requests like transferring funds, changing their email address, and so forth. If the victim is an administrative account, CSRF can compromise the entire web application. [1] - (OWASP) 4 | 5 | CSRF as an attack vector is often overlooked, and represents a significant "chaos" factor unless the validation is handled at the highest level: the framework. This allows web application and API authors to have significant control over a vital layer of security. 6 | 7 | The [Perfect Sessions](https://github.com/PerfectlySoft/PerfectDocs/blob/master/guide/sessions.md) module includes support for CSRF configuration. 8 | 9 | If you have included Perfect Sessions or any of its datasource-specific implementations in your Packages.swift file, you already have CSRF support. 10 | 11 | ## Relevant Examples 12 | 13 | * [Perfect-Session-Memory-Demo](https://github.com/PerfectExamples/Perfect-Session-Memory-Demo) 14 | 15 | 16 | ## Configuration 17 | 18 | An example CSRF Configuration might look like this: 19 | 20 | ``` swift 21 | SessionConfig.CSRF.checkState = true 22 | SessionConfig.CSRF.failAction = .fail 23 | SessionConfig.CSRF.checkHeaders = true 24 | SessionConfig.CSRF.acceptableHostnames.append("http://www.example.com") 25 | SessionConfig.CSRF.requireToken = true 26 | ``` 27 | 28 | ### SessionConfig.CSRF.checkState 29 | 30 | This is the "master switch". If enabled, CSRF will be enabled for all routes. 31 | 32 | ### SessionConfig.CSRF.failAction 33 | 34 | This specifies the action to take if the CSRF validation fails. The possible options are: 35 | 36 | * `.fail` - Execute an immediate halt. No further processing will be done, and an HTTP Status `406 Not Acceptable` is generated. 37 | * `.log` - Processing will continue, however the event will be recorded in the log. 38 | * `.none` - Processing will continue, no action is taken. 39 | 40 | ### SessionConfig.CSRF.acceptableHostnames 41 | 42 | An array of host names that are compared in the following section for "origin" match acceptance. 43 | 44 | 45 | ### SessionConfig.CSRF.checkHeaders 46 | 47 | If the `CORS.checkheader` is configured as `true`, origin and host headers are checked for validity. 48 | 49 | * The `Origin`, `Referrer` or `X-Forwarded-For` headers must be populated ("origin"). 50 | * If the "origin" is specified in `SessionConfig.CSRF.acceptableHostnames`, the CSRF check will continue to the next phase and the following checks are skipped. 51 | * The `Host` or `X-Forwarded-Host` header must be present ("host"). 52 | * The "host" and "origin" values must match exactly. 53 | 54 | 55 | 56 | ### SessionConfig.CSRF.requireToken 57 | 58 | When set to true, this setting will enforce all POST requests to include a "_csrf" param, or if the content type header is "application/json" then an associated "X-CSRF-Token" header must be sent with the request. The content of the header or parameter should match the `request.session.data["csrf"]` value. This value is set automatically at session start. 59 | 60 | ### Session state 61 | 62 | While not a configuration param, it is worth noting that if `SessionConfig.CSRF.checkState` is true, no POST request will be accepted if the session is "new". This is a deliberate position supported by security recommendations. 63 | 64 | 65 | 66 | 67 | 68 | [1] - OWASP, Cross-Site Request Forgery (CSRF): [https://www.owasp.org/index.php/Cross-Site\_Request\_Forgery\_(CSRF)](https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)) -------------------------------------------------------------------------------- /guide.zh_CN/python.md: -------------------------------------------------------------------------------- 1 | # Perfect - Python 2 | 3 | 本项目提供了在Swift服务器应用上直接引用Python 2.7函数库的简便方法。 4 | 5 | 本项目采用Swift Package Manager 软件包管理器编译,是[Perfect](https://github.com/PerfectlySoft/Perfect) 项目的一部分,但是也可以独立运行 6 | 7 | 在使用之前请准备好最新的Swift 4.0 工具链 8 | 9 | ## Linux 编译事项 10 | 11 | 首先请确保 libpython2.7-dev 已经在 Ubuntu 16.04 上正确安装: 12 | 13 | ``` 14 | $ sudo apt-get install libpython2.7-dev 15 | ``` 16 | 17 | ## MacOS 编译事项 18 | 19 | 请确定 Xcode 9.0 以上版本已经正确安装 20 | 21 | ## 快速上手 22 | 23 | 首先在Package.swift中增加依存关系: 24 | 25 | ``` swift 26 | .Package(url: "https://github.com/PerfectlySoft/Perfect-Python.git", majorVersion: 3) 27 | ``` 28 | 29 | 然后将下列头文件导入Swift源代码: 30 | 31 | ``` swift 32 | import PythonAPI 33 | import PerfectPython 34 | ``` 35 | 36 | 请注意在任何程序调用之前,必须调用`Py_Initialize()`函数初始化python嵌入环境: 37 | 38 | ``` swift 39 | Py_Initialize() 40 | ``` 41 | 42 | ### 导入Python函数库模块 43 | 44 | 使用 `PyObj` 类对象用于导入python模块。下列参考范例中,一个名为`/tmp/clstest.py`的脚本被动态导入到当前Swift运行环境: 45 | 46 | ``` swift 47 | let pymod = try PyObj(path: "/tmp", import: "clstest") 48 | ``` 49 | 50 | ### 访问Python变量 51 | 52 | 导入模块后,您可以使用`PyObj.load()`函数加载任何一个变量;也可以反过来用 `PyObj.save()`命令保存当前变量为一个新的值。 53 | 54 | 比如,以下python脚本中有个叫做 `stringVar` 的字符串变量: 55 | 56 | ``` python 57 | stringVar = 'Hello, world' 58 | ``` 59 | 60 | 那么要取得这个字符串的值只需要这样做: 61 | 62 | ``` swift 63 | if let str = pymod.load("stringVar")?.value as? String { 64 | print(str) 65 | // 会打印变量的字符串值 "Hello, world!" 66 | } 67 | ``` 68 | 69 | 此时您还可以为该变量直接写入新的字符串值: 70 | 71 | ``` swift 72 | try pymod.save("stringVar", newValue: "Hola, 🇨🇳🇨🇦!") 73 | ``` 74 | 75 | 76 | **注意** 目前,Perfect-Python仅支持如下Swift / Python数据类型自动转换: 77 | 78 | Python 类型|Swift 类型|备注 79 | ----------|---------|------- 80 | int|Int| 81 | float|Double| 82 | str|String| 83 | list|[Any]|递归转换 84 | dict|[String:Any]|递归转换 85 | 86 | 比如,您可以把一个字符串 `String` 转换为 `PyObj`,通过 `let pystr = "Hello".python()` 或者 `let pystr = try PyObj(value:"Hello")` 完成转换。 87 | 88 | 反过来,如果要把 `PyObj` 类转换为Swift数据类型,比如字符串,则仍然有两种方法:`let str = pystr.value as? String` 和 `let str = String(python: pystr)`。 89 | 90 | ### 执行Python函数 91 | 92 | 方法 `PyObj.call()` 用于带参数执行某个python函数。以如下python脚本为例: 93 | 94 | ``` python 95 | def mymul(num1, num2): 96 | return num1 * num2 97 | ``` 98 | 99 | Perfect-Python 可以用下列方法封装并调用以上函数,您所需要注意的仅仅是其函数名称以及参数。其中函数名称用字符串代替,而参数用一个数组表达: 100 | 101 | ``` swift 102 | if let res = pymod.call("mymul", args: [2,3])?.value as? Int { 103 | print(res) 104 | // 结果为 6 105 | } 106 | ``` 107 | 108 | ### Python类对象 109 | 110 | 请同样使用 `PyObj.load()` 函数用于家在Python类对象,但是注意后面一定要紧跟一个`PyObj.construct()` 用于初始化类对象实例。该方法同样支持用一个任意类型的数组作为参数进行对象构造。 111 | 112 | 假设如下脚本的典型python类对象 `Person`,该类有两个属性姓名`name` 和年龄`age`,还有一个名为“自我介绍”的类对象方法`intro()`: 113 | 114 | ``` python 115 | class Person: 116 | def __init__(self, name, age): 117 | self.name = name 118 | self.age = age 119 | 120 | def intro(self): 121 | return 'Name: ' + self.name + ', Age: ' + str(self.age) 122 | ``` 123 | 124 | 在Swift中初始化上述类对象的方法需要进行以下两步走: 125 | 126 | ``` swift 127 | if let personClass = pymod.load("Person"), 128 | let person = personClass.construct(["rocky", 24]) { 129 | // person is now the object instance 130 | } 131 | ``` 132 | 133 | 之后就可以访问类实例的属性变量和方法了,如同上文所提到的普通变量和函数调用的方法一样: 134 | 135 | ``` swift 136 | if let name = person.load("name")?.value as? String, 137 | let age = person.load("age")?.value as? Int, 138 | let intro = person.call("intro", args: [])?.value as? String { 139 | print(name, age, intro) 140 | } 141 | ``` 142 | 143 | ### 回调函数 144 | 145 | 参考以下python代码,此时如果执行 `x = caller('Hello', callback)` 则可以将函数作为参数进行回调: 146 | 147 | ``` python 148 | def callback(msg): 149 | return 'callback: ' + msg 150 | 151 | def caller(info, func): 152 | return func(info) 153 | ``` 154 | 155 | 在Swift中等效的代码平淡无奇,只不过将待调函数作为参数而已:: 156 | 157 | ``` swift 158 | if let fun = pymod.load("callback"), 159 | let result = pymod.call("caller", args: ["Hello", fun]), 160 | let v = result.value as? String { 161 | print(v) 162 | // 结果是 "callback: Hello" 163 | } 164 | ``` -------------------------------------------------------------------------------- /guide.zh_CN/HTTPRequest.md: -------------------------------------------------------------------------------- 1 | # HTTPRequest请求对象 2 | 3 | 当处理一个HTTP请求时,所有客户端的互动操作都是通过HTTPRequest请求对象和HTTPResponse响应对象实现的。 4 | 5 | HTTPRequest对象包含了客户端浏览器发过来的全部数据,包括请求消息头、查询参数、POST表单数据以及其它所有相关信息,比如客户IP地址和URL变量。 6 | 7 | HTTPRequest对象将采用`application/x-www-form-urlencoded`编码格式对客户请求进行解析解码。而如果请求中采用`multipart/form-data`“多段”编码方式,则HTTP请求可以把各种未处理的原始格式表单传输过来。当处理“多段”表单数据时,HTTPRequest对象会为请求上传的文件自动创建临时目录并执行解码。这些文件会在请求过程中一直保持直到请求处理完毕,随后自动被删除。 8 | 9 | 以上涉及到的各种属性和函数都是HTTPRequest请求协议的部分内容。 10 | 11 | ### Meta Data元数据 12 | 13 | HTTPRequest对象还提供一些并非被客户端浏览器显式说明的数据,比如客户端和服务器地址,TCP端口,以及对应分类的文档根目录。 14 | 15 | 客户端服务器的地址是IP地址和端口的成对信息: 16 | 17 | ``` swift 18 | /// 连接到客户端的IP地址和端口。 19 | var remoteAddress: (host: String, port: UInt16) { get } 20 | /// 服务器方的IP地址和监听端口。 21 | var serverAddress: (host: String, port: UInt16) { get } 22 | ``` 23 | 24 | 当服务器创建时,您可以为其设置一个正式的服务器名`CNAME`。很多情况下这个名称非常有用,比如为服务器创建完整的连接。当HTTPRequest创建后,服务器会为其应用`CNAME`,可以从下列属性进行访问: 25 | 26 | ``` swift 27 | /// 服务器正式名称 28 | var serverName: String { get } 29 | ``` 30 | 31 | 服务器的文档根目录是为静态文件准备的。如果您不准备提供静态内容,则可以不需要设置文档根目录。文档根目录是在服务器启动之前预先配置好的。当HTTPRequest对象创建时会自动包括这个文档根目录。如果需要访问如Mustache模板这类的静态资源,最好就此准备好文档根目录: 32 | 33 | ``` swift 34 | /// 服务器文档根目录,用于静态文件存储和服务提供。 35 | var documentRoot: String { get } 36 | ``` 37 | 38 | ### Request Line请求文本行 39 | 40 | HTTPRequest请求文本行包含了请求的方法、路径、查询参数和HTTP协议标识符。典型的HTTPRequest请求文本行形如: 41 | 42 | ``` swift 43 | GET /path?q1=v1&q2=v2 HTTP/1.1 44 | ``` 45 | 46 | HTTPRequest将解析这个请求并转化为以下属性。查询参数`queryParams`是以一个键/值构造的(字典)数组: 47 | 48 | ``` swift 49 | /// The HTTP request method. 50 | var method: HTTPMethod { get set } 51 | /// The request path. 52 | var path: String { get set } 53 | /// The parsed and decoded query/search arguments. 54 | var queryParams: [(String, String)] { get } 55 | /// The HTTP protocol version. For example (1, 0), (1, 1), (2, 0) 56 | var protocolVersion: (Int, Int) { get } 57 | ``` 58 | 59 | 在请求响应路由处理过程中,路由URI字串可有多个URL变量组成,可以被解析为一个字典: 60 | 61 | ``` swift 62 | /// 在请求句柄中的URL变量。 63 | var urlVariables: [String:String] { get set } 64 | ``` 65 | 66 | HTTPRequest对象还提供完整的URI请求信息,与其它URL编码查询参数`query parameters`同样方式保存请求路径: 67 | 68 | ``` swift 69 | /// 返回完整的URI唯一资源标识符。 70 | var uri: String { get } 71 | ``` 72 | 73 | ### Client Headers客户请求消息头 74 | 75 | 客户请求的消息头可以用命名访问,或者用遍历方式访问,。HTTPRequest会自动解析所有HTTP cookie的字段名称和字段值。所有可能的请求消息头字段由枚举类型```HTTPRequestHeader.Name```说明,其中还包含了一个可以自定义的```.custom(name: String)```消息头字段 76 | 77 | 在收到请求后是可以自行设置消息头内容的。在HTTPRequest过滤器中非常有用,因为它们可以重写部分消息头内容: 78 | 79 | ``` swift 80 | /// 返回请求的消息头变量值。 81 | func header(_ named: HTTPRequestHeader.Name) -> String? 82 | /// 为响应增加一个消息头 83 | /// 不会检查目前是否存在重复的消息头。 84 | func addHeader(_ named: HTTPRequestHeader.Name, value: String) 85 | /// 这只消息头变量的属性值。 86 | /// 如果之前消息头已经存在则现有属性值会被替换。 87 | func setHeader(_ named: HTTPRequestHeader.Name, value: String) 88 | /// 遍历所有当前消息头内的属性数据。 89 | var headers: AnyIterator<(HTTPRequestHeader.Name, String)> { get } 90 | ``` 91 | 92 | Cookie可以通过键/值数组进行访问。 93 | 94 | ``` swift 95 | /// 返回目前请求内的所有cookie的键/值。 96 | var cookies: [(String, String)] 97 | ``` 98 | 99 | ### GET和POST参数 100 | 101 | 关于GET和POST参数的详细讨论,详见[使用表单数据](formData.md)。 102 | 103 | ### 消息体数据 104 | 105 | 对于“application/x-www-form-urlencoded”和“multipart/form-data”编码类型来说,HTTPRequest对象会通过```postParams```或```postFileUploads```方法自动解码请求内容。 106 | 107 | 关于文件上传的详细处理方法,请参考[文件上传](fileUploads.md)。关于```postParams```函数的详细使用,请参考[使用表单数据](formData.md)。 108 | 109 | 其它编码类型的消息体数据不会被解码,只能通过原始字节流或者原始字符串的方式进行访问。比如,对于客户发送的JSON数据,使用字符串的方法会比较有用。 110 | 111 | HTTPRequest对象可以通过以下方法访问消息体数据: 112 | 113 | ``` swift 114 | /// 以原始字节流的方式获取POST消息体数据。 115 | /// 如果POST内容类型是multipart/form-data多段表单的话,下面的方法返回为nil。 116 | var postBodyBytes: [UInt8]? { get set } 117 | /// 如果需要的话,POST消息体数据会以UTF-8编码方式解码为一个字符串。 118 | /// 如果POST内容类型是multipart/form-data多段表单的话,下面的方法返回为nil。 119 | var postBodyString: String? { get } 120 | ``` 121 | 122 | **⚠️注意⚠️** 如果请求编码为“multipart/form-data”则```postBodyBytes```属性会为空。否则,该属性总会返回请求的消息体数据,无论请求用哪一种内容类型编码。 123 | 124 | 注意```postBodyString``` 属性会尝试将数据从UTF-8字节流转化为一个字符串。如果没有消息体数据或者数据无法以UTF-8解码,则返回内容为空。 125 | -------------------------------------------------------------------------------- /guide/zip.md: -------------------------------------------------------------------------------- 1 | # Perfect's Zip Toolkit 2 | 3 | Perfect provides a wrapper around the minizip C library and implements a set of convenience functions for use when the PerfectZip package is imported. 4 | 5 | ## Relevant Examples 6 | 7 | * [Perfect-Zip-Example](https://github.com/PerfectExamples/Perfect-Zip-Example) 8 | 9 | ## Getting Started 10 | 11 | On MacOS, install minizip using Homebrew: 12 | 13 | ``` 14 | brew install minizip 15 | ``` 16 | 17 | On Ubuntu, install minizip: 18 | 19 | ``` 20 | apt-get install libminizip-dev 21 | ``` 22 | 23 | In addition to the PerfectLib, you will need the Perfect-Zip dependency in the Package.swift file: 24 | 25 | ``` swift 26 | .Package(url: "https://github.com/PerfectlySoft/Perfect-Zip.git", majorVersion: 3) 27 | ``` 28 | 29 | ## Using Perfect Zip 30 | 31 | The two main functions of the Perfect Zip module are to zip files, or unzip a zip file. 32 | 33 | ### Declaring an instance of the Zip class 34 | 35 | Before initiating compression or decompression, an instance of the class must be created: 36 | 37 | ``` swift 38 | let myVar = Zip() 39 | ``` 40 | 41 | 42 | ### Zip 43 | 44 | To compress a file, use the `.zipFiles(...)` method. 45 | 46 | ``` swift 47 | zipFiles( 48 | paths: [String], 49 | zipFilePath: String, 50 | overwrite: Bool, 51 | password: String? 52 | ) -> ZipStatus 53 | ``` 54 | 55 | This method returns the success/fail status of the operation as a `ZipStatus` enum. 56 | 57 | #### Parameters: 58 | 59 | * **paths:** The array of file paths to add to the zip file 60 | * **zipFilePath:** The path and filename of the destination zip file 61 | * **overwrite:** A boolean declaring the behaviour to attempt when the destination zip file exists 62 | * **password:** The password string to use for password-protecting the zip file. Optional. Leave empty or omit to create an unprotected zip file. 63 | 64 | 65 | ### UnZip 66 | 67 | To decompress a file, use the `.unzipFile(...)` method. 68 | 69 | ``` swift 70 | unzipFile( 71 | source: String, 72 | destination: String, 73 | overwrite: Bool, 74 | password: String = "" 75 | ) -> ZipStatus 76 | ``` 77 | 78 | This method returns the success/fail status of the operation as a `ZipStatus` enum. 79 | 80 | #### Parameters: 81 | 82 | * **source:** The file path of zipped file 83 | * **destination:** The path to the directory into which the contents of the zip file are to be placed 84 | * **overwrite:** A boolean declaring the behaviour to attempt when the destination directory or file exists 85 | * **password:** The password string to use for decrypting a password-protected zip file. Optional. 86 | 87 | 88 | ### ZipStatus 89 | 90 | The `ZipStatus` values are as follows: 91 | 92 | * .FileNotFound 93 | * .UnzipFail 94 | * .ZipFail 95 | * .ZipCannotOverwrite 96 | * .ZipSuccess 97 | 98 | The enum has a `.description` variable which returns human-readable descriptors of each enum value. 99 | 100 | * .FileNotFound - **"File not found."** 101 | * .UnzipFail - **"Failed to unzip file."** 102 | * .ZipFail - **"Failed to zip file."** 103 | * .ZipCannotOverwrite - **"Cannot overwrite destination file."** 104 | * .ZipSuccess - **"Success."** 105 | 106 | 107 | 108 | ## Usage 109 | 110 | The following will zip the specified directory: 111 | 112 | ``` swift 113 | import PerfectZip 114 | 115 | let myZip = Zip() 116 | 117 | let thisZipFile = "/path/to/ZipFile.zip" 118 | let sourceDir = "/path/to/files/" 119 | 120 | let ZipResult = myZip.zipFiles( 121 | paths: [sourceDir], 122 | zipFilePath: thisZipFile, 123 | overwrite: true, password: "" 124 | ) 125 | print("ZipResult Result: \(ZipResult.description)") 126 | ``` 127 | 128 | To unzip a file: 129 | 130 | ``` swift 131 | import PerfectZip 132 | 133 | let myZip = Zip() 134 | 135 | let sourceDir = "/path/to/files/" 136 | let thisZipFile = "/path/to/ZipFile.zip" 137 | 138 | let UnZipResult = myZip.unzipFile( 139 | source: thisZipFile, 140 | destination: sourceDir, 141 | overwrite: true 142 | ) 143 | print("Unzip Result: \(UnZipResult.description)") 144 | ``` 145 | -------------------------------------------------------------------------------- /guide.zh_CN/HTTPResponse.md: -------------------------------------------------------------------------------- 1 | # HTTPResponse响应对象 2 | 3 | 当处理一个HTTP请求时,所有客户端的互动操作都是通过HTTPRequest请求对象和HTTPResponse响应对象实现的。 4 | 5 | HTTPResponse响应对象包含了所有即将作为响应发回到客户端浏览器的数据。该对象包含了HTTP状态代码和对应消息,HTTP消息头,以及页面内容数据。HTTPResponse还包含了流数据和浏览器推送的能力,可以控制完成请求或终止请求。 6 | 7 | 以下内容为HTTPResponse对象协议的属性和方法,非该对象的内容会另行说明。 8 | 9 | ### HTTP状态 10 | 11 | HTTP状态用于说明请求是否成功。以返回错误或者采取其它操作。默认情况下HTTPResponse对象包含一个200 OK状态。这个状态也可以设置为任何一个其它值。HTTP状态代码由`HTTPResponseStatus`枚举管理,也包含了可以自定义的`.custom(code: Int, message: String)`状态 12 | 13 | 返回状态见如下属性: 14 | 15 | ``` swift 16 | /// HTTP响应状态 17 | var status: HTTPResponseStatus { get set } 18 | ``` 19 | 20 | ### Response响应消息头 21 | 22 | 响应消息头可以从对象内获取、设置或者遍历。常用的官方消息头命名参见```HTTPResponseHeader.Name```枚举。该枚举还包含了可以自定义消息头名称的```.custom(name: String)```类型。 23 | 24 | ``` swift 25 | /// 返回响应消息头内容值。 26 | func header(_ named: HTTPResponseHeader.Name) -> String? 27 | /// 在响应内容中增加一条消息头数据。 28 | /// 注意添加操作不会自动检查该条目数据是否已经存在或者重复。 29 | func addHeader(_ named: HTTPResponseHeader.Name, value: String) 30 | /// 设置消息头数据 31 | /// 如果消息头中该数据项已经存在,则已存在的数据值会被覆盖。 32 | func setHeader(_ named: HTTPResponseHeader.Name, value: String) 33 | /// 遍历所有目前对象内的消息头数据。 34 | var headers: AnyIterator<(HTTPResponseHeader.Name, String)> { get } 35 | ``` 36 | 37 | HTTPResponse对象还提供用于设置HTTP cookies(即浏览器内保存临时用户信息的一种方法)的更高级支持,即创建一个cookie对象,然后增加到响应对象中去。 38 | 39 | ``` swift 40 | /// 以下结构用于设置响应内容中的cookie数据 41 | public struct HTTPCookie { 42 | /// Cookie 构造函数 43 | public init(name: String, // 名称 44 | value: String, // 值 45 | domain: String?, // 所在域名 46 | expires: Expiration?, // 有效期 47 | path: String?, // 路径 48 | secure: Bool?, // 时间 49 | httpOnly: Bool?) // 只限于http协议 50 | } 51 | ``` 52 | 53 | 用以下函数可以将Cookie增加到HTTPResponse响应 54 | 55 | ``` swift 56 | /// 在输出响应时增加一个cookie。 57 | func addCookie(_ cookie: HTTPCookie) 58 | ``` 59 | 60 | 当cookie增加到HTTP响应之后会被格式化为“Set-Cookie”消息头 61 | 62 | ### Body Data消息体数据 63 | 64 | 响应的当前消息体数据可以通过以下属性管理: 65 | 66 | ``` swift 67 | /// 待发出给客户端浏览器的消息体数据. 68 | /// 每个数据块送出后都会被清空。 69 | var bodyBytes: [UInt8] { get set } 70 | ``` 71 | 72 | 数据可以直接追加到数组中去,或者通过下列函数进行内容追加。下列函数可以完全改变消息体数据内容,或者仅仅是追加二进制无符号整数字节或者字符串。字符串会被转换为UTF-8编码。最后一个函数允许将一个`[String:Any]`字典转化为一个JSON字符串。 73 | 74 | ``` swift 75 | /// 追加二进制字节流到消息体。 76 | func appendBody(bytes: [UInt8]) 77 | /// 向响应输出追加字符串, 78 | /// 所有字符串都会被编码为UTF-8 的 [UInt8]二进制字节数组 79 | func appendBody(string: String) 80 | /// 清除当前消息体,并用新的二进制字节数组内容代替。 81 | func setBody(bytes: [UInt8]) 82 | /// 清除当前消息体,并用新的字符串内容代替, 83 | /// 所有字符串都会别编码为UTF-8 的 [UInt8]二进制字节数组 84 | func setBody(string: String) 85 | /// 清除当前消息体,并用JSON字典代替。该JSON字典会被转化为一个JSON字符串,并转化为UTF-8 的 [UInt8]二进制字节数组。 86 | func setBody(json: [String:Any]) throws 87 | ``` 88 | 89 | 当对客户端发送响应时,一定要注意必须包括消息头内的响应内容长度字节数。当HTTPResponse响应对象开始将积累的数据发给客户端时,会检查其消息头长度是否设置。如果没有设置,则响应对象会根据```bodyBytes```数组长度进行统计。 90 | 91 | 以下函数用于推送所有响应消息头和消息体数据: 92 | 93 | ``` swift 94 | /// 将现有全部消息头和消息体数据推送给客户端浏览器。 95 | /// 可能会一次或多次调用。 96 | func push(callback: (Bool) -> ()) 97 | ``` 98 | 99 | 通常没有必要调用这个函数,因为系统会在请求完成后自动推送所有响应数据。但是某种情况下可能用户系统直接控制这个过程。 100 | 101 | 比如,服务器需要发送一个非常大的文件,这种情况下不可能把全部内容读到内存中设置消息体数据。因此首先要将文件长度设置为待响应的消息体长度,然后进行分段读写,即先读出一部分文件数据,以读出来的内容设置消息体,然后重复调用```push```函数直到全部文件内容发送完毕。```push```函数会在完成时调用参数中的callback回调函数,并带一个布尔变量作为调用回调函数的参数。如果内容已经成功发送给客户端浏览器,则该布尔变量会被设置为真值。如果布尔值为假,则意味着该请求失败,也不会需要再采取任何操作。 102 | 103 | ### 流媒体处理模式 104 | 105 | 某些情况下,响应内容的长度无法轻易确定。比如,如果是在线的视频流或者音乐流,则这种情况下是无法设置消息头中的内容长度的。此时,请将HTTPResponse设置为流媒体模式,这样响应会以HTTP数据块编码的方式进行发送。流媒体不要求设置响应内容长度单位,相反,只需要对响应增加消息体并调用```push```函数。如果push成功,则消息体会被清空然后可以继续追加更多数据。请持续调用```push```直到请求完成,或者push函数会给回调函数发一个```false```表示响应失败。 106 | 107 | ``` swift 108 | /// 确定响应切换为流媒体输出模式。 109 | /// 最大的区别就是响应内容长度是未知的。 110 | var isStreaming: Bool { get set } 111 | ``` 112 | 113 | 如果确定要使用流媒体模式,则需要在给客户端浏览器推送数据前,将```isStreaming```属性设置为真。 114 | 115 | ### 完成请求 116 | 117 | **⚠️注意⚠️**:当请求完成时,**必须调用** HTTPResponse的```completed```函数。必须确认所有的数据已经完全送达给客户,随后TCP连接会被关闭,无论是不是处于HTTP keep-alive活动状态,直到有新的请求开始处理。 118 | 119 | ``` swift 120 | /// 意味着请求已经全部完成。 121 | /// 所有未发送的消息头数据和消息体数据此时都会推送给客户。 122 | /// 一旦调用完成则后续操作均失去意义。 123 | func completed() 124 | ``` 125 | -------------------------------------------------------------------------------- /guide/StORM-Setting-up-a-class.md: -------------------------------------------------------------------------------- 1 | # Setting up a class to use StORM 2 | 3 | The first thing you need to do is import the Library: 4 | 5 | ``` swift 6 | import PostgresStORM 7 | // or 8 | import SQLiteStORM 9 | ``` 10 | 11 | When you create a new class that will model a database table, it should inherit from a parent StORM class. This will provide most of the goodness you will need. 12 | 13 | If you are using PostgreSQL: 14 | 15 | ``` swift 16 | class User: PostgresStORM { 17 | } 18 | ``` 19 | 20 | If you are using SQLite: 21 | 22 | ``` swift 23 | class User: SQLiteStORM { 24 | } 25 | ``` 26 | 27 | Next, you will need to add properties to the class. They will mirror closely your columns in the associated table. It's strongly recommended that you keep non-standard characters out of the column names and therefore property names too. 28 | 29 | The first property should be your table's primary key. Convention is that this column is called `id` but in reality it can be any valid column name. Common data types for primary keys in SQL datasources are integers, strings, and UUIDs. If your primary key is not an auto-incrementing integer sequence, take care with setting your id and maintaining integrity. 30 | 31 | ``` swift 32 | // NOTE: First param in class should be the ID. 33 | var id : Int = 0 34 | var firstname : String = "" 35 | var lastname : String = "" 36 | var email : String = "" 37 | ``` 38 | 39 | You will see above that the default values are set for each property rather than set via an `init()`. This is simply to make this explanation easier. If you choose to write your own `init()` function, include `super.init()` and beware of the need to add the connection property. 40 | 41 | ### Specifying the table 42 | 43 | The table name is to be manually specified in a function. This is to avoid any possible collision with the introspection that is performed on the class for database operations. 44 | 45 | Include a function to specify the table as follows: 46 | 47 | ``` swift 48 | override open func table() -> String { 49 | return "users" 50 | } 51 | ``` 52 | 53 | ### Class-specific assignment functions 54 | 55 | In order for the class to do certain things, such as taking a result set from the database and converting the rows to an array of classed objects, add two functions to the class: 56 | 57 | ``` swift 58 | override func to(_ this: StORMRow) { 59 | id = this.data["id"] as? Int ?? 0 60 | firstname = this.data["firstname"] as? String ?? "" 61 | lastname = this.data["lastname"] as? String ?? "" 62 | email = this.data["email"] as? String ?? "" 63 | } 64 | 65 | func rows() -> [User] { 66 | var rows = [User]() 67 | for i in 0.. String { 97 | return "users" 98 | } 99 | 100 | override func to(_ this: StORMRow) { 101 | id = this.data["id"] as? Int ?? 0 102 | firstname = this.data["firstname"] as? String ?? "" 103 | lastname = this.data["lastname"] as? String ?? "" 104 | email = this.data["email"] as? String ?? "" 105 | } 106 | 107 | func rows() -> [User] { 108 | var rows = [User]() 109 | for i in 0..Void)),邮件发送函数,参数为回调函数。 102 | 回调函数包括以下三个参数,详细含义请参考 Perfect-CURL `performFully()`函数: 103 | - code: Int,邮件服务器响应代码,正常值是零。 104 | - header: String,邮件响应头数据字符串。 105 | - body: String,邮件响应体数据字符串。 106 | 107 | 108 | ## 总结 109 | 110 | 以下是发送邮件过程的简明结构: 111 | 112 | ``` swift 113 | import PerfectSMTP 114 | 115 | let client = SMTPClient(url: "smtp://smtp.gmx.com", username: "yourname@youraddress.com", password:"yourpassword") 116 | 117 | var email = EMail(client: client) 118 | 119 | email.subject = "邮件主题" 120 | email.content = "邮件内容" 121 | 122 | email.cc.append(Recipient(address: "who@where.com")) 123 | 124 | do { 125 | try email.send { code, header, body in 126 | /// 从邮件服务器响应的结果 127 | print(code) 128 | }//end send 129 | }catch(let err) { 130 | /// 出错了 131 | } 132 | ``` 133 | 134 | ## 示范程序 135 | 136 | 在github上有一个完整邮件发送程序供参考: 137 | [Perfect SMTP Demo](https://github.com/PerfectExamples/Perfect-SMTP-Demo) 138 | 139 | ## SMTPS协议注意事项 140 | 141 | 我们收到了很多关于google smtp示范的要求,在此感谢 @ucotta 、@james,当然还有来自Perfect 官方专业支持的 @iamjono,以下说明可能会为您的gmail客户端开发有所帮助: ⚠️*SMTPClient 的地址应该设置为 `smtps://smtp.gmail.com:465`,并且在谷歌设置中一定要开启“降低安全性以便于某些传统客户端访问”*⚠️ 142 | 143 | 以下是使用SMTPS加密协议的示范例子(以gmail为例) 144 | 145 | ``` swift 146 | import PerfectSMTP 147 | 148 | let client = SMTPClient(url: "smtps://smtp.gmail.com:465", username: "yourname@gmail.com", password:"yourpassword") 149 | 150 | var email = EMail(client: client) 151 | 152 | email.subject = "a topic" 153 | email.content = "a message" 154 | 155 | email.cc.append(Recipient(address: "who@where.com")) 156 | 157 | do { 158 | try email.send { code, header, body in 159 | /// response info from mail server 160 | print(code) 161 | }//end send 162 | }catch(let err) { 163 | /// something wrong 164 | } 165 | ``` 166 | -------------------------------------------------------------------------------- /guide/cors.md: -------------------------------------------------------------------------------- 1 | ## CORS (Cross Origin Resource Sharing) Security 2 | 3 | Cross-Origin Resource Sharing (CORS) is an important part of the "open web", and as such, no framework is complete without enabling support for CORS. 4 | 5 | Monsur Hossain from [html5rocks.com introduces CORS very effectively](https://www.html5rocks.com/en/tutorials/cors/): 6 | 7 | > APIs are the threads that let you stitch together a rich web experience. But this experience has a hard time translating to the browser, where the options for cross-domain requests are limited to techniques like JSON-P (which has limited use due to security concerns) or setting up a custom proxy (which can be a pain to set up and maintain). 8 | > 9 | > Cross-Origin Resource Sharing (CORS) is a W3C spec that allows cross-domain communication from the browser. By building on top of the XMLHttpRequest object, CORS allows developers to work with the same idioms as same-domain requests. 10 | > 11 | > The use-case for CORS is simple. Imagine the site alice.com has some data that the site bob.com wants to access. This type of request traditionally wouldn’t be allowed under the browser’s same origin policy. However, by supporting CORS requests, alice.com can add a few special response headers that allows bob.com to access the data. 12 | > 13 | > As you can see from this example, CORS support requires coordination between both the server and client. Luckily, if you are a client-side developer you are shielded from most of these details. The rest of this article shows how clients can make cross-origin requests, and how servers can configure themselves to support CORS. 14 | 15 | The [Perfect Sessions](https://github.com/PerfectlySoft/PerfectDocs/blob/master/guide/sessions.md) module includes support for CORS configuration, enabling your API and assets to be available or secured in the way you wish. 16 | 17 | If you have included Perfect Sessions or any of its datasource-specific implementations in your Packages.swift file, you already have CORS support; however, it is **off** by default. 18 | 19 | ## Relevant Examples 20 | 21 | * [Perfect-Session-Memory-Demo](https://github.com/PerfectExamples/Perfect-Session-Memory-Demo) 22 | 23 | ## Configuration 24 | 25 | ``` swift 26 | // Enabled, true or false. 27 | // Default is false. 28 | SessionConfig.CORS.enabled = true 29 | 30 | // Array of acceptable hostnames for incoming requests 31 | // To enable CORS on all, have a single entry, * 32 | SessionConfig.CORS.acceptableHostnames = ["*"] 33 | 34 | // However if you wish to enable specific domains: 35 | SessionConfig.CORS.acceptableHostnames.append("http://www.test-cors.org") 36 | 37 | // Wildcards can also be used at the start or end of hosts 38 | SessionConfig.CORS.acceptableHostnames.append("*.example.com") 39 | SessionConfig.CORS.acceptableHostnames.append("http://www.domain.*") 40 | 41 | // Array of acceptable methods 42 | public var methods: [HTTPMethod] = [.get, .post, .put] 43 | 44 | // An array of custom headers allowed 45 | public var customHeaders = [String]() 46 | 47 | // Access-Control-Allow-Credentials true/false. 48 | // Standard CORS requests do not send or set any cookies by default. 49 | // In order to include cookies as part of the request enable the client to do so by setting to true 50 | public var withCredentials = false 51 | 52 | // Max Age (seconds) of request / OPTION caching. 53 | // Set to 0 for no caching (default) 54 | public var maxAge = 3600 55 | 56 | ``` 57 | 58 | When a CORS request is submitted to the server, if there is no match then the CORS specific headers are not generated in the OPTIONS response, which will instruct the browser that it cannot accept the resource. 59 | 60 | If the server determines that CORS headers should be generated, the following headers are sent with the response: 61 | 62 | ``` swift 63 | // An array of allowable HTTP Methods. 64 | // In the case of the above configuration example: 65 | Access-Control-Allow-Methods: GET, POST, PUT 66 | 67 | // If the origin is acceptable the origin will be echoed back to the requester 68 | // (even if configured with *) 69 | Access-Control-Allow-Origin: http://www.test-cors.org 70 | 71 | // If the server wishes cookies to be sent along with requests, 72 | // this should return true 73 | Access-Control-Allow-Credentials: true 74 | 75 | // The length of time the OPTIONS request can be cached for. 76 | Access-Control-Max-Age: 3600 77 | ``` 78 | 79 | An excellent resource for testing CORS entitlements and responses is available at [http://www.test-cors.org](http://www.test-cors.org) --------------------------------------------------------------------------------