├── .gitignore ├── LICENSE ├── LICENSE.zh_CN ├── Package.swift ├── README.md ├── README.zh_CN.md ├── Sources └── PerfectFastCGI │ ├── FastCGIRequest.swift │ ├── FastCGIResponse.swift │ └── FastCGIServer.swift └── Tests ├── LinuxMain.swift └── PerfectFastCGITests └── PerfectFastCGITests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xcuserstate 23 | 24 | ## Obj-C/Swift specific 25 | *.hmap 26 | *.ipa 27 | *.dSYM.zip 28 | *.dSYM 29 | 30 | ## Playgrounds 31 | timeline.xctimeline 32 | playground.xcworkspace 33 | 34 | # Swift Package Manager 35 | # 36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 37 | # Packages/ 38 | .build/ 39 | 40 | # CocoaPods 41 | # 42 | # We recommend against adding the Pods directory to your .gitignore. However 43 | # you should judge for yourself, the pros and cons are mentioned at: 44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 45 | # 46 | # Pods/ 47 | 48 | # Carthage 49 | # 50 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 51 | # Carthage/Checkouts 52 | 53 | Carthage/Build 54 | 55 | # fastlane 56 | # 57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 58 | # screenshots whenever they are needed. 59 | # For more information about the recommended setup visit: 60 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 61 | 62 | fastlane/report.xml 63 | fastlane/Preview.html 64 | fastlane/screenshots 65 | fastlane/test_output 66 | 67 | Packages/ 68 | PerfectFastCGI.xcodeproj/ 69 | .DS_Store 70 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE.zh_CN: -------------------------------------------------------------------------------- 1 | Apache许可证 2 | 2.0版 2004年1月 3 | http://www.apache.org/licenses/ 4 | 5 | 关于使用、复制和分发的条款 6 | 7 | 定义 8 | "许可证"是指根据本文件第1到第9部分关于使用、复制和分发的条款。 9 | 10 | "许可证颁发者"是指版权所有者或者由版权所有者授权许可证的实体。 11 | 12 | "法律实体"是指实施实体和进行控制的所有其它实体受该实体控制,或者受该实体集中控制。根据此定义,"控制"是指(i)让无论是否签订协议的上述实体,进行指导或管理的直接权利或间接权利,或者(ii)拥有百分之五十(50%)或以上已发行股票的所有者,或者(iii)上述实体的实权所有者。 13 | 14 | "用户"(或"用户的")是指行使本许可证所授予权限的个人或法律实体。 15 | 16 | "源程序"形式是指对包括但不限于软件源代码、文件源程序和配置文件进行修改的首选形式。 17 | 18 | "目标"形式是指对源程序形式进行机械转换或翻译的任何形式,包括但不限于对编译的目标代码,生成的文件以及转换为其它媒体类型。 19 | 20 | "作品"是指根据本许可证所制作的源程序形式或目标形式的著作,在著作中包含的或附加的版权通知(在下面附录中提供了一个示例)。 21 | 22 | "衍生作品"是指基于作品(或从作品衍生而来)的源程序形式或目标形式的任何作品,以及编辑修订、注释、详细描述或其它修订等构成原创著作作品的整体。根据本许可证,衍生作品不得包括与作品及其衍生作品分离之作品,或仅与作品及其衍生作品的接口相链接(或按名称结合)之作品。 23 | 24 | "贡献"是指任何著作作品,包括作品的原始版本和对该作品或衍生作品所做的任何修订或补充,意在提交给许可证颁发者以让版权所有者或代表版权所有者的授权个人或法律实体包含在其作品中。根据此定义,"提交"一词表示发送给许可证颁发者或其代表人,任何电子的、口头的或书面的交流信息形式,包括但不限于在由许可证颁发者或者代表其管理的电子邮件清单、源代码控制系统、以及发布跟踪系统上为讨论和提高作品的交流,但不包括由版权所有者以书面形式明显标注或指定为"非贡献"的交流活动。 25 | 26 | "贡献者"是指许可证颁发者和代表从许可证颁发者接受之贡献的并随后包含在作品之贡献中的任何个人或法律实体。 27 | 28 | 版权许可证的授予。根据本许可证的条款,每个贡献者授予用户永久性的、全球性的、非专有性的、免费的、无版权费的、不可撤销的版权许可证以源程序形式或目标形式复制、准备衍生作品、公开显示、公开执行、授予分许可证、以及分发作品和这样的衍生作品。 29 | 专利许可证的授予。根据本许可证的条款,每个贡献者授予用户永久性的、全球性的、非专有性的、免费的、无版权费的、不可撤销的(除在本部分进行说明)专利许可证对作品进行制作、让人制作、使用、提供销售、销售、进口和其它转让,且这样的许可证仅适用于在所递交作品的贡献中因可由单一的或多个这样的贡献者授予而必须侵犯的申请专利。如果用户对任何实体针对作品或作品中所涉及贡献提出因直接性或贡献性专利侵权而提起专利法律诉讼(包括交互诉讼请求或反索赔),那么根据本许可证,授予用户针对作品的任何专利许可证将在提起上述诉讼之日起终止。 30 | 重新分发。用户可在任何媒介中复制和分发作品或衍生作品之副本,无论是否修订,还是以源程序形式或目标形式,条件是用户需满足下列条款: 31 | 用户必须为作品或衍生作品的任何其他接收者提供本许可证的副本;并且 32 | 用户必须让任何修改过的文件附带明显的通知,声明用户已更改文件;并且 33 | 用户必须从作品的源程序形式中保留衍生作品源程序形式的用户所分发的所有版权、专利、商标和属性通知,但不包括不属于衍生作品任何部分的类似通知;并且 34 | 如果作品将"通知"文本文件包括为其分发作品的一部分,那么用户分发的任何衍生作品中须至少在下列地方之一包括,在这样的通知文件中所包含的属性通知的可读副本,但不包括那些不属于衍生作品任何部分的通知:在作为衍生作品一部分而分发的通知文本文件中;如果与衍生作品一起提供则在源程序形式或文件中;或者通常作为第三方通知出现的时候和地方,在衍生作品中产生的画面中。通知文件的内容仅供信息提供,并未对许可证进行修改。用户可在其分发的衍生作品中在作品的通知文本后或作为附录添加自己的属性通知,条件是附加的属性通知不得构成修改本许可证。 35 | 用户可以为自身所做出的修订添加自己的版权声明并可对自身所做出修订内容或为这样的衍生作品作为整体的使用、复制或分发提供附加或不同的条款,条件是用户对作品的使用、复制和分发必须符合本许可证中声明的条款。 36 | 37 | 贡献的提交。除非用户明确声明,在作品中由用户向许可证颁发者的提交若要包含在贡献中,必须在无任何附加条款下符合本许可证的条款。尽管上面如此规定,执行许可证颁发者有关贡献的条款时,任何情况下均不得替代或修改任何单独许可证协议的条款。 38 | 商标。本许可证并未授予用户使用许可证颁发者的商号、商标、服务标记或产品名称,除非将这些名称用于合理性和惯例性描述作品起源和复制通知文件的内容时。 39 | 保证否认条款。除非因适用法律需要或书面同意,许可证颁发者以"按原样"基础提供作品(并且每个贡献者提供其贡献),无任何明示的或暗示的保证或条件,包括但不限于关于所有权、不侵权、商品适销性、或适用性的保证或条件。用户仅对使用或重新分发作品的正确性负责,并需承担根据本许可证行使权限时的任何风险。 40 | 责任限制条款。在任何情况下并根据任何法律,无论是因侵权(包括过失)或根据合同,还是其它原因,除非根据适用法律需要(例如故意行为和重大过失行为)或经书面同意,即使贡献者事先已被告知发生损害的可能性,任何贡献者不就用户因使用本许可证或不能使用或无法使用作品(包括但不限于商誉损失、停工、计算机失效或故障,或任何商业损坏或损失)而造成的损失,包括直接的、非直接的、特殊的、意外的或间接的字符损坏而负责。 41 | 接受保证或附加责任。重新分发作品或及其衍生作品时,用户可选择提供或为符合本许可证承担之支持、担保、赔偿或其它职责义务和/或权利而收取费用。但是,在承担上述义务时,用户只可代表用户本身和用户本身责任来执行,无需代表任何其它贡献者,并且用户仅可保证、防护并保持每个贡献者不受任何因此而产生的责任或对因用户自身承担这样的保证或附加责任而对这样的贡献者所提出的索赔。 42 | 条款结束 43 | 44 | 附录:如何向用户作品中应用Apache许可证。 45 | 46 | 若要向用户作品应用Apache许可证,请附加下列样本通知,将括号"[]"中的字段以用户自身的区分信息来替换(但不包括括号)。文本必须以文件格式适当的注释句法包含在其中。另外建议将文件名或类别名以及目的说明包含在相同的"打印页"上作为版权通知,以更加容易的区分出第三方档案。 47 | 48 | 版权所有[yyyy][版权所有者的名称] 49 | 50 | 根据2.0版本Apache许可证("许可证")授权; 51 | 根据本许可证,用户可以不使用此文件。 52 | 用户可从下列网址获得许可证副本: 53 | 54 | http://www.apache.org/licenses/LICENSE-2.0 55 | 56 | 除非因适用法律需要或书面同意, 57 | 根据许可证分发的软件是基于"按原样"基础提供, 58 | 无任何明示的或暗示的保证或条件。 59 | 详见根据许可证许可下,特定语言的管辖权限和限制。 60 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.1 2 | // Package.swift 3 | // PerfectFastCGI 4 | // 5 | // Created by Kyle Jessup on 4/20/16. 6 | // Copyright (C) 2016 PerfectlySoft, Inc. 7 | // 8 | //===----------------------------------------------------------------------===// 9 | // 10 | // This source file is part of the Perfect.org open source project 11 | // 12 | // Copyright (c) 2015 - 2016 PerfectlySoft Inc. and the Perfect project authors 13 | // Licensed under Apache License v2.0 14 | // 15 | // See http://perfect.org/licensing.html for license information 16 | // 17 | //===----------------------------------------------------------------------===// 18 | // 19 | 20 | import PackageDescription 21 | 22 | let package = Package( 23 | name: "PerfectFastCGI", 24 | products: [ 25 | .library(name: "PerfectFastCGI", targets: ["PerfectFastCGI"]) 26 | ], 27 | dependencies: [ 28 | .package(url: "https://github.com/PerfectlySoft/Perfect-HTTP.git", from: "3.0.0") 29 | ], 30 | targets: [ 31 | .target(name: "PerfectFastCGI", dependencies: ["PerfectHTTP"]) 32 | ] 33 | ) 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Perfect-FastCGI [简体中文](README.zh_CN.md) 2 | 3 |

4 | 5 | Get Involed with Perfect! 6 | 7 |

8 | 9 |

10 | 11 | Star Perfect On Github 12 | 13 | 14 | Stack Overflow 15 | 16 | 17 | Follow Perfect on Twitter 18 | 19 | 20 | Join the Perfect Slack 21 | 22 |

23 | 24 |

25 | 26 | Swift 3.0 27 | 28 | 29 | Platforms OS X | Linux 30 | 31 | 32 | License Apache 33 | 34 | 35 | PerfectlySoft Twitter 36 | 37 | 38 | Slack Status 39 | 40 |

41 | 42 | Perfect server can run with either its built-in HTTP 1.1 system or with this FastCGI based server. 43 | 44 | This server can run with any FastCGI enabled webserver over either UNIX socket files or TCP. 45 | 46 | ## Apache 2.4 47 | To run with Apache 2.4, build and install the mod_perfect FastCGI module: 48 | 49 | [Perfect-FastCGI-Apache2.4](https://github.com/PerfectlySoft/Perfect-FastCGI-Apache2.4) 50 | 51 | ## NGINX 52 | Instructions for running with NGINX: 53 | 54 | [NGINX](https://github.com/PerfectlySoft/Perfect/wiki/NGINX) 55 | 56 | ## Starter Template 57 | Get started with a FastCGI based project template: 58 | 59 | [PerfectTemplateFCGI](https://github.com/PerfectlySoft/PerfectTemplateFCGI) 60 | 61 | ## Building 62 | 63 | Add this package as a dependency: 64 | 65 | ```swift 66 | .Package(url:"https://github.com/PerfectlySoft/Perfect-FastCGI.git", majorVersion: 2, minor: 0) 67 | ``` 68 | -------------------------------------------------------------------------------- /README.zh_CN.md: -------------------------------------------------------------------------------- 1 | # Perfect-FastCGI 服务器插件 [English](https://github.com/PerfectlySoft/Perfect-FastCGI) 2 | 3 |

4 | 5 | Get Involed with Perfect! 6 | 7 |

8 | 9 |

10 | 11 | Star Perfect On Github 12 | 13 | 14 | Chat on Gitter 15 | 16 | 17 | Follow Perfect on Twitter 18 | 19 | 20 | Join the Perfect Slack 21 | 22 |

23 | 24 |

25 | 26 | Swift 3.0 27 | 28 | 29 | Platforms OS X | Linux 30 | 31 | 32 | License Apache 33 | 34 | 35 | PerfectlySoft Twitter 36 | 37 | 38 | Join the chat at https://gitter.im/PerfectlySoft/Perfect 39 | 40 | 41 | Slack Status 42 | 43 |

44 | 45 | Perfect服务器即可以用内建HTTP 1.1协议方式独立运行,也可以作为一个FastCGI插件,在FastCGI下运行。 46 | 47 | 此外,Perfect服务器还能兼容不同的FastCGI服务器设置,如UNIX socket files套接字文件或者TCP。 48 | ### 问题报告 49 | 50 | 我们目前正在过渡到使用JIRA来处理所有源代码资源合并申请、修复漏洞以及其它有关问题。因此,GitHub 的“issues”问题报告功能已经被禁用了。 51 | 52 | 如果您发现了问题,或者希望为改进本文提供意见和建议,[请在这里指出](http://jira.perfect.org:8080/servicedesk/customer/portal/1). 53 | 54 | 在您开始之前,请参阅[目前待解决的问题清单](http://jira.perfect.org:8080/projects/ISS/issues). 55 | 56 | ## Apache 2.4 57 | 如果需要在Apache 2.4下使用FastCGI,请参考下列内容编译和安装mod_perfect的FastCGI模块: 58 | 59 | [Perfect-FastCGI-Apache2.4](https://github.com/PerfectlySoft/Perfect-FastCGI-Apache2.4) 60 | 61 | ## NGINX 62 | 在NGINX下安装运行Perfect的方法: 63 | 64 | [NGINX](https://github.com/PerfectlySoft/Perfect/wiki/NGINX) 65 | 66 | ## 快速上手的模板 67 | 参考以下链接可获得从FastCGI快速上手的工程模板: 68 | 69 | [PerfectTemplateFCGI](https://github.com/PerfectlySoft/PerfectTemplateFCGI) 70 | 71 | ## 编译 72 | 73 | 为了能够正常编译运行,请修改Package.swift文件增加以下内容: 74 | 75 | ```swift 76 | .Package(url:"https://github.com/PerfectlySoft/Perfect-FastCGI.git", majorVersion: 2, minor: 0) 77 | ``` 78 | -------------------------------------------------------------------------------- /Sources/PerfectFastCGI/FastCGIRequest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FastCGIRequest.swift 3 | // PerfectFastCGI 4 | // 5 | // Created by Kyle Jessup on 2016-06-27. 6 | // Copyright (C) 2016 PerfectlySoft, Inc. 7 | // 8 | //===----------------------------------------------------------------------===// 9 | // 10 | // This source file is part of the Perfect.org open source project 11 | // 12 | // Copyright (c) 2015 - 2016 PerfectlySoft Inc. and the Perfect project authors 13 | // Licensed under Apache License v2.0 14 | // 15 | // See http://perfect.org/licensing.html for license information 16 | // 17 | //===----------------------------------------------------------------------===// 18 | // 19 | 20 | import PerfectLib 21 | import PerfectNet 22 | import PerfectHTTP 23 | 24 | final class FastCGIRequest: HTTPRequest { 25 | var requestId: UInt16 = 0 26 | var lastRecordType: UInt8 = 0 27 | 28 | var method = HTTPMethod.get 29 | var path = "" 30 | var pathComponents: [String] { return path.filePathComponents } 31 | var queryString = "" 32 | var protocolVersion = (1, 0) 33 | var remoteAddress = (host: "", port: 0 as UInt16) 34 | var serverAddress = (host: "", port: 0 as UInt16) 35 | var serverName = "" 36 | var documentRoot = "./webroot" 37 | let connection: NetTCP 38 | var urlVariables = [String:String]() 39 | var scratchPad = [String:Any]() 40 | var mimes: MimeReader? 41 | var workingBuffer = [UInt8]() 42 | 43 | private var headerStore = Dictionary() 44 | 45 | lazy var queryParams: [(String, String)] = { 46 | return self.deFormURLEncoded(string: self.queryString) 47 | }() 48 | 49 | var headers: AnyIterator<(HTTPRequestHeader.Name, String)> { 50 | var g = self.headerStore.makeIterator() 51 | return AnyIterator<(HTTPRequestHeader.Name, String)> { 52 | guard let n = g.next() else { 53 | return nil 54 | } 55 | return (n.key, n.value) 56 | } 57 | } 58 | 59 | lazy var postParams: [(String, String)] = { 60 | 61 | if let mime = self.mimes { 62 | return mime.bodySpecs.filter { $0.file == nil }.map { ($0.fieldName, $0.fieldValue) } 63 | } else if let bodyString = self.postBodyString { 64 | return self.deFormURLEncoded(string: bodyString) 65 | } 66 | return [(String, String)]() 67 | }() 68 | 69 | var postBodyBytes: [UInt8]? { 70 | get { 71 | if let _ = mimes { 72 | return nil 73 | } 74 | return workingBuffer 75 | } 76 | set { 77 | if let nv = newValue { 78 | workingBuffer = nv 79 | } else { 80 | workingBuffer.removeAll() 81 | } 82 | } 83 | } 84 | 85 | var postBodyString: String? { 86 | guard let bytes = postBodyBytes else { 87 | return nil 88 | } 89 | if bytes.isEmpty { 90 | return "" 91 | } 92 | return UTF8Encoding.encode(bytes: bytes) 93 | } 94 | var postFileUploads: [MimeReader.BodySpec]? { 95 | guard let mimes = self.mimes else { 96 | return nil 97 | } 98 | return mimes.bodySpecs 99 | } 100 | 101 | var contentType: String? { 102 | return self.headerStore[.contentType] 103 | } 104 | 105 | typealias StatusCallback = (HTTPResponseStatus) -> () 106 | 107 | init(connection: NetTCP) { 108 | self.connection = connection 109 | } 110 | 111 | func header(_ named: HTTPRequestHeader.Name) -> String? { 112 | return headerStore[named] 113 | } 114 | 115 | func addHeader(_ named: HTTPRequestHeader.Name, value: String) { 116 | guard let existing = headerStore[named] else { 117 | self.headerStore[named] = value 118 | return 119 | } 120 | if named == .cookie { 121 | self.headerStore[named] = existing + "; " + value 122 | } else { 123 | self.headerStore[named] = existing + ", " + value 124 | } 125 | } 126 | 127 | func setHeader(_ named: HTTPRequestHeader.Name, value: String) { 128 | headerStore[named] = value 129 | } 130 | 131 | func setHeader(named: String, value: String) { 132 | let lowered = named.lowercased() 133 | setHeader(HTTPRequestHeader.Name.fromStandard(name: lowered), value: value) 134 | } 135 | 136 | private func addParam(name: String, value: String) { 137 | let httpChars = "HTTP_" 138 | switch name { 139 | case "REQUEST_METHOD": 140 | method = HTTPMethod.from(string: value) 141 | case "SERVER_PROTOCOL": 142 | if value == "HTTP/1.1" { 143 | protocolVersion = (1, 1) 144 | } 145 | case "DOCUMENT_ROOT": 146 | documentRoot = value 147 | case "SERVER_NAME": 148 | serverName = value 149 | case "SERVER_ADDR": 150 | serverAddress.host = value 151 | case "SERVER_PORT": 152 | serverAddress.port = UInt16(value) ?? 0 153 | case "REMOTE_ADDR": 154 | remoteAddress.host = value 155 | case "REMOTE_PORT": 156 | remoteAddress.port = UInt16(value) ?? 0 157 | case "QUERY_STRING": 158 | queryString = value 159 | case "REQUEST_URI": 160 | path = value 161 | default: 162 | let nameChars = name 163 | if nameChars.starts(with: httpChars) { 164 | let newName = String(nameChars[nameChars.index(nameChars.startIndex, offsetBy: httpChars.count).. (), callback: @escaping StatusCallback) { 181 | self.connection.readBytesFully(count: fcgiBaseRecordSize, timeoutSeconds: fcgiTimeoutSeconds) { 182 | [weak self] 183 | b in 184 | guard let recBytes = b else { 185 | return continuation(nil) 186 | } 187 | let record = FastCGIRecord() 188 | record.version = recBytes[0] 189 | record.recType = recBytes[1] 190 | record.requestId = ((UInt16(recBytes[3]) << 8) | UInt16(recBytes[2])).netToHost 191 | record.contentLength = ((UInt16(recBytes[5]) << 8) | UInt16(recBytes[4])).netToHost 192 | record.paddingLength = recBytes[6]; 193 | record.reserved = recBytes[7] 194 | self?.readRecordContent(record: record, continuation: continuation, callback: callback) 195 | } 196 | } 197 | 198 | func readRecordContent(record rec: FastCGIRecord, continuation: @escaping (FastCGIRecord?) -> (), callback: @escaping StatusCallback) { 199 | guard rec.contentLength > 0 else { 200 | return self.readRecordPadding(record: rec, continuation: continuation, callback: callback) 201 | } 202 | self.connection.readBytesFully(count: Int(rec.contentLength), timeoutSeconds: fcgiTimeoutSeconds) { 203 | [weak self] 204 | b in 205 | if let contentBytes = b { 206 | rec.content = contentBytes 207 | self?.readRecordPadding(record: rec, continuation: continuation, callback: callback) 208 | } else { 209 | continuation(nil) 210 | } 211 | } 212 | } 213 | 214 | func readRecordPadding(record rec: FastCGIRecord, continuation: @escaping (FastCGIRecord?) -> (), callback: StatusCallback) { 215 | guard rec.paddingLength > 0 else { 216 | return continuation(rec) 217 | } 218 | self.connection.readBytesFully(count: Int(rec.paddingLength), timeoutSeconds: fcgiTimeoutSeconds) { 219 | b in 220 | if let paddingBytes = b { 221 | rec.padding = paddingBytes 222 | continuation(rec) 223 | } else { 224 | continuation(nil) 225 | } 226 | } 227 | } 228 | 229 | func handleRecord(_ fcgiRecord: FastCGIRecord, callback: @escaping StatusCallback) { 230 | switch fcgiRecord.recType { 231 | case fcgiBeginRequest: 232 | guard let content = fcgiRecord.content else { 233 | return callback(.badRequest) 234 | } 235 | // FastCGIBeginRequestBody UInt16 role, UInt8 flags 236 | let role: UInt16 = ((UInt16(content[1]) << 8) | UInt16(content[0])).netToHost 237 | let flags: UInt8 = content[2] 238 | addHeader(.custom(name: "x-fcgi-role"), value: String(role)) 239 | addHeader(.custom(name: "x-fcgi-flags"), value: String(flags)) 240 | requestId = fcgiRecord.requestId 241 | case fcgiParams: 242 | if let bytes = fcgiRecord.content , fcgiRecord.contentLength > 0 { 243 | var idx = 0 244 | repeat { 245 | // sizes are either one byte or 4 246 | var sz = Int32(bytes[idx]) 247 | idx += 1 248 | if (sz & 0x80) != 0 { // name length 249 | sz = (sz & 0x7f) << 24 250 | sz += (Int32(bytes[idx]) << 16) 251 | idx += 1 252 | sz += (Int32(bytes[idx]) << 8) 253 | idx += 1 254 | sz += Int32(bytes[idx]) 255 | idx += 1 256 | } 257 | var vsz = Int32(bytes[idx]) 258 | idx += 1 259 | if (vsz & 0x80) != 0 { // value length 260 | vsz = (vsz & 0x7f) << 24 261 | vsz += (Int32(bytes[idx]) << 16) 262 | idx += 1 263 | vsz += (Int32(bytes[idx]) << 8) 264 | idx += 1 265 | vsz += Int32(bytes[idx]) 266 | idx += 1 267 | } 268 | if sz > 0 { 269 | let idx2 = idx + Int(sz) 270 | let name = UTF8Encoding.encode(bytes: bytes[idx.. 0 { 280 | putPostData(content) 281 | } else { // done initiating the request. run with it 282 | return callback(.ok) 283 | } 284 | case fcgiData: 285 | if let content = fcgiRecord.content , fcgiRecord.contentLength > 0 { 286 | addHeader(.custom(name: "x-fcgi-data"), value: UTF8Encoding.encode(bytes: content)) 287 | } 288 | case fcgiXStdin: 289 | if let content = fcgiRecord.content , Int(fcgiRecord.contentLength) == MemoryLayout.size { 290 | let one = UInt32(content[0]) 291 | let two = UInt32(content[1]) 292 | let three = UInt32(content[2]) 293 | let four = UInt32(content[3]) 294 | let size = ((four << 24) + (three << 16) + (two << 8) + one).netToHost 295 | readXStdin(size: Int(size), callback: callback) 296 | return 297 | } 298 | default: 299 | print("Unhandled FastCGI record type \(fcgiRecord.recType)") 300 | } 301 | lastRecordType = fcgiRecord.recType 302 | readRequest(callback: callback) 303 | } 304 | 305 | func readXStdin(size: Int, callback: @escaping StatusCallback) { 306 | connection.readSomeBytes(count: size) { 307 | [weak self] 308 | b in 309 | guard let bytes = b else { 310 | self?.connection.close() 311 | return callback(.requestTimeout) // died. timed out. errorered 312 | } 313 | self?.putPostData(bytes) 314 | let remaining = size - bytes.count 315 | if remaining == 0 { 316 | self?.lastRecordType = fcgiStdin 317 | self?.readRequest(callback: callback) 318 | } else { 319 | self?.readXStdin(size: remaining, callback: callback) 320 | } 321 | } 322 | } 323 | 324 | func putPostData(_ b: [UInt8]) { 325 | if self.workingBuffer.count == 0 && self.mimes == nil { 326 | if let contentType = self.contentType , contentType.starts(with: "multipart/form-data") { 327 | self.mimes = MimeReader(contentType) 328 | } 329 | } 330 | if let mimes = self.mimes { 331 | return mimes.addToBuffer(bytes: b) 332 | } else { 333 | self.workingBuffer.append(contentsOf: b) 334 | } 335 | } 336 | 337 | private func deFormURLEncoded(string: String) -> [(String, String)] { 338 | return string.split(separator: "&").map(String.init).compactMap { 339 | let d = $0.split(separator: "=").compactMap { String($0).stringByDecodingURL } 340 | if d.count == 2 { return (d[0], d[1]) } 341 | if d.count == 1 { return (d[0], "") } 342 | return nil 343 | } 344 | } 345 | } 346 | -------------------------------------------------------------------------------- /Sources/PerfectFastCGI/FastCGIResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FastCGIResponse.swift 3 | // PerfectFastCGI 4 | // 5 | // Created by Kyle Jessup on 2016-06-27. 6 | // Copyright (C) 2016 PerfectlySoft, Inc. 7 | // 8 | //===----------------------------------------------------------------------===// 9 | // 10 | // This source file is part of the Perfect.org open source project 11 | // 12 | // Copyright (c) 2015 - 2016 PerfectlySoft Inc. and the Perfect project authors 13 | // Licensed under Apache License v2.0 14 | // 15 | // See http://perfect.org/licensing.html for license information 16 | // 17 | //===----------------------------------------------------------------------===// 18 | // 19 | 20 | import PerfectLib 21 | import PerfectNet 22 | import PerfectHTTP 23 | 24 | final class FastCGIResponse: HTTPResponse { 25 | 26 | let request: HTTPRequest 27 | let requestId: UInt16 28 | var status: HTTPResponseStatus = .ok 29 | var isStreaming = false 30 | var bodyBytes = [UInt8]() 31 | var completedCallback: (() -> ())? 32 | var wroteHeaders = false 33 | var headerStore = Array<(HTTPResponseHeader.Name, String)>() 34 | var headers: AnyIterator<(HTTPResponseHeader.Name, String)> { 35 | var g = self.headerStore.makeIterator() 36 | return AnyIterator<(HTTPResponseHeader.Name, String)> { 37 | g.next() 38 | } 39 | } 40 | 41 | var connection: NetTCP { 42 | return request.connection 43 | } 44 | 45 | var handlers: IndexingIterator<[RequestHandler]>? 46 | 47 | init(request: FastCGIRequest) { 48 | self.request = request 49 | self.requestId = request.requestId 50 | let net = request.connection 51 | self.completedCallback = { 52 | self.completedCallback = nil 53 | self.push { 54 | ok in 55 | guard ok else { 56 | net.close() 57 | return 58 | } 59 | let finalBytes = self.makeEndRequestBody(requestId: Int(request.requestId), appStatus: self.status.code, protocolStatus: fcgiRequestComplete) 60 | net.write(bytes: finalBytes) { 61 | _ in 62 | net.close() 63 | } 64 | } 65 | } 66 | } 67 | 68 | func completed() { 69 | if let cb = self.completedCallback { 70 | cb() 71 | } 72 | } 73 | 74 | func next() { 75 | if let n = handlers?.next() { 76 | n(request, self) 77 | } else { 78 | completed() 79 | } 80 | } 81 | 82 | func header(_ named: HTTPResponseHeader.Name) -> String? { 83 | for (n, v) in headerStore where n == named { 84 | return v 85 | } 86 | return nil 87 | } 88 | 89 | func addHeader(_ name: HTTPResponseHeader.Name, value: String) -> Self { 90 | headerStore.append((name, value)) 91 | return self 92 | } 93 | 94 | func setHeader(_ name: HTTPResponseHeader.Name, value: String) -> Self { 95 | var fi = [Int]() 96 | for i in 0.. ()) { 110 | wroteHeaders = true 111 | var responseString = "Status: \(status)\r\n" 112 | for (n, v) in headers { 113 | responseString.append("\(n.standardName): \(v)\r\n") 114 | } 115 | responseString.append("\r\n") 116 | let bytes = makeStdoutBody(requestId: Int(requestId), data: [UInt8](responseString.utf8)) 117 | connection.write(bytes: bytes) { 118 | _ in 119 | self.pushBody(callback: callback) 120 | } 121 | } 122 | 123 | func pushBody(callback: @escaping (Bool) -> ()) { 124 | guard !bodyBytes.isEmpty else { 125 | return callback(true) 126 | } 127 | let bytes = makeStdoutBody(requestId: Int(requestId), data: bodyBytes) 128 | connection.write(bytes: bytes) { 129 | wrote in 130 | self.bodyBytes.removeAll() 131 | callback(wrote == bytes.count) 132 | } 133 | } 134 | 135 | func push(callback: @escaping (Bool) -> ()) { 136 | if !wroteHeaders { 137 | pushHeaders(callback: callback) 138 | } else { 139 | pushBody(callback: callback) 140 | } 141 | } 142 | 143 | func makeEndRequestBody(requestId rid: Int, appStatus: Int, protocolStatus: Int) -> [UInt8] { 144 | let b = Bytes() 145 | b.import8Bits(from: fcgiVersion1) 146 | .import8Bits(from: fcgiEndRequest) 147 | .import16Bits(from: UInt16(rid).hostToNet) 148 | .import16Bits(from: UInt16(8).hostToNet) 149 | .import8Bits(from: 0) 150 | .import8Bits(from: 0) 151 | .import32Bits(from: UInt32(appStatus).hostToNet) 152 | .import8Bits(from: UInt8(protocolStatus)) 153 | .import8Bits(from: 0) 154 | .import8Bits(from: 0) 155 | .import8Bits(from: 0) 156 | return b.data 157 | } 158 | 159 | func makeStdoutBody(requestId rid: Int, data: [UInt8], firstPos: Int, count: Int) -> [UInt8] { 160 | let b = Bytes() 161 | if count > fcgiBodyChunkSize { 162 | b.importBytes(from: makeStdoutBody(requestId: rid, data: data, firstPos: firstPos, count: fcgiBodyChunkSize)) 163 | .importBytes(from: makeStdoutBody(requestId: rid, data: data, firstPos: fcgiBodyChunkSize + firstPos, count: count - fcgiBodyChunkSize)) 164 | } else { 165 | let padBytes = count % 8 166 | b.import8Bits(from: fcgiVersion1) 167 | .import8Bits(from: fcgiStdout) 168 | .import16Bits(from: UInt16(rid).hostToNet) 169 | .import16Bits(from: UInt16(count).hostToNet) 170 | .import8Bits(from: UInt8(padBytes)) 171 | .import8Bits(from: 0) 172 | if firstPos == 0 && count == data.count { 173 | b.importBytes(from: data) 174 | } else { 175 | b.importBytes(from: data[firstPos..<(firstPos+count)]) 176 | } 177 | if padBytes > 0 { 178 | for _ in 1...padBytes { 179 | b.import8Bits(from: 0) 180 | } 181 | } 182 | } 183 | return b.data 184 | } 185 | 186 | func makeStdoutBody(requestId rid: Int, data: [UInt8]) -> [UInt8] { 187 | return makeStdoutBody(requestId: rid, data: data, firstPos: 0, count: data.count) 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /Sources/PerfectFastCGI/FastCGIServer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FastCGIServer.swift 3 | // PerfectLib 4 | // 5 | // Created by Kyle Jessup on 7/6/15. 6 | // Copyright (C) 2015 PerfectlySoft, Inc. 7 | // 8 | //===----------------------------------------------------------------------===// 9 | // 10 | // This source file is part of the Perfect.org open source project 11 | // 12 | // Copyright (c) 2015 - 2016 PerfectlySoft Inc. and the Perfect project authors 13 | // Licensed under Apache License v2.0 14 | // 15 | // See http://perfect.org/licensing.html for license information 16 | // 17 | //===----------------------------------------------------------------------===// 18 | // 19 | 20 | import PerfectNet 21 | import PerfectThread 22 | import PerfectLib 23 | import PerfectHTTP 24 | 25 | #if os(Linux) 26 | import SwiftGlibc 27 | let S_IRGRP = (S_IRUSR >> 3) 28 | let S_IWGRP = (S_IWUSR >> 3) 29 | let S_IRWXU = (__S_IREAD|__S_IWRITE|__S_IEXEC) 30 | let S_IRWXG = (S_IRWXU >> 3) 31 | let S_IRWXO = (S_IRWXG >> 3) 32 | #else 33 | import Darwin 34 | #endif 35 | 36 | let fcgiVersion1: UInt8 = 1 37 | let fcgiBeginRequest: UInt8 = 1 38 | let fcgiEndRequest: UInt8 = 3 39 | let fcgiParams: UInt8 = 4 40 | let fcgiStdin: UInt8 = 5 41 | let fcgiStdout: UInt8 = 6 42 | let fcgiData: UInt8 = 8 43 | let fcgiXStdin: UInt8 = 50 44 | let fcgiRequestComplete = 0 45 | let fcgiTimeoutSeconds = 5.0 46 | let fcgiBaseRecordSize = 8 47 | let fcgiBodyChunkSize = 0xFFFF 48 | 49 | class FastCGIRecord { 50 | 51 | var version: UInt8 = 0 52 | var recType: UInt8 = 0 53 | var requestId: UInt16 = 0 54 | var contentLength: UInt16 = 0 55 | var paddingLength: UInt8 = 0 56 | var reserved: UInt8 = 0 57 | var content: [UInt8]? = nil 58 | var padding: [UInt8]? = nil 59 | } 60 | 61 | /// A server for the FastCGI protocol. 62 | /// Listens for requests on either a named pipe or a TCP socket. Once started, it does not stop or return outside of a catastrophic error. 63 | /// When a request is received, the server will instantiate a `WebRequest`/`WebResponse` pair and they will handle the remainder of the request. 64 | public class FastCGIServer { 65 | 66 | private var net: NetTCP? 67 | /// Switch to user after binding socket file 68 | public var runAsUser: String? 69 | 70 | /// Routing support 71 | private var routes = Routes() 72 | private var routeNavigator: RouteNavigator? 73 | 74 | /// Empty public initializer 75 | public init() { 76 | 77 | } 78 | 79 | /// Add the Routes to this server. 80 | public func addRoutes(_ routes: Routes) { 81 | self.routes.add(routes) 82 | } 83 | 84 | /// Start the server on the indicated named pipe 85 | public func start(namedPipe name: String) throws { 86 | if access(name, F_OK) != -1 { 87 | // exists. remove it 88 | unlink(name) 89 | } 90 | let pipe = NetNamedPipe() 91 | try pipe.bind(address: name) 92 | pipe.listen() 93 | chmod(name, mode_t(S_IRWXU|S_IRWXO|S_IRWXG)) 94 | if let runAs = self.runAsUser { 95 | try PerfectServer.switchTo(userName: runAs) 96 | } 97 | self.net = pipe 98 | defer { pipe.close() } 99 | print("Starting FastCGI server on named pipe "+name) 100 | self.start() 101 | } 102 | 103 | /// Start the server on the indicated TCP port and optional address 104 | public func start(port prt: UInt16, bindAddress: String = "0.0.0.0") throws { 105 | let socket = NetTCP() 106 | try socket.bind(port: prt, address: bindAddress) 107 | socket.listen() 108 | defer { socket.close() } 109 | print("Starting FastCGi server on \(bindAddress):\(prt)") 110 | self.start() 111 | } 112 | 113 | func start() { 114 | self.routeNavigator = self.routes.navigator 115 | 116 | guard let n = self.net else { 117 | return 118 | } 119 | n.forEachAccept { 120 | [weak self] 121 | net in 122 | guard let n = net else { 123 | return 124 | } 125 | Threading.dispatch { 126 | self?.handleConnection(net: n) 127 | } 128 | } 129 | } 130 | 131 | func handleConnection(net: NetTCP) { 132 | let fcgiReq = FastCGIRequest(connection: net) 133 | fcgiReq.readRequest { [weak self] 134 | status in 135 | if case .ok = status { 136 | self?.runRequest(fcgiReq) 137 | } else { 138 | net.close() 139 | } 140 | } 141 | } 142 | 143 | func runRequest(_ request: FastCGIRequest) { 144 | let resp = FastCGIResponse(request: request) 145 | if let nav = routeNavigator, 146 | let handlers = nav.findHandlers(pathComponents: request.pathComponents, webRequest: request) { 147 | resp.handlers = handlers.makeIterator() 148 | resp.next() 149 | } else { 150 | resp.status = .notFound 151 | resp.appendBody(string: "The file \(request.path) was not found.") 152 | resp.completed() 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import PerfectFastCGITests 3 | 4 | XCTMain([ 5 | testCase(PerfectFastCGITests.allTests), 6 | ]) 7 | -------------------------------------------------------------------------------- /Tests/PerfectFastCGITests/PerfectFastCGITests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import PerfectFastCGI 3 | 4 | class PerfectFastCGITests: XCTestCase { 5 | func testExample() { 6 | 7 | } 8 | 9 | static var allTests : [(String, (PerfectFastCGITests) -> () throws -> Void)] { 10 | return [ 11 | ("testExample", testExample), 12 | ] 13 | } 14 | } 15 | --------------------------------------------------------------------------------