├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Extra ├── SwiftEngineCore │ ├── .gitignore │ ├── Makefile │ ├── Package.swift │ ├── README.md │ ├── Sources │ │ └── SwiftEngineCore │ │ │ ├── Request.swift │ │ │ ├── RequestContext.swift │ │ │ ├── RequestHandler.swift │ │ │ ├── Response.swift │ │ │ ├── SwiftEngineCore.swift │ │ │ └── UploadedFile.swift │ └── Tests │ │ ├── LinuxMain.swift │ │ └── SwiftEngineCoreTests │ │ ├── SwiftEngineCoreTests.swift │ │ └── XCTestManifests.swift └── templates │ ├── etc │ └── swiftengine │ │ └── magic │ │ └── main.swift │ └── var │ └── swiftengine │ └── www │ ├── common.swift │ └── default.swift ├── LICENSE.txt ├── Makefile ├── Package.resolved ├── Package.swift ├── README.md ├── Sources ├── SEProcessor │ └── main.swift ├── SEProcessorLib │ ├── ContentType.swift │ ├── SECommon.swift │ ├── SECompiler.swift │ ├── SEGlobals.swift │ ├── SEResponse.swift │ ├── SERoute.swift │ └── SEShell.swift └── SwiftEngineServer │ ├── ByteBuffer+ForString.swift │ ├── HTTPHandler.swift │ ├── SEHTTPHandler.swift │ ├── SELogger.swift │ ├── SEMiddlewareHandler.swift │ ├── SEShell.swift │ ├── SocketAddress+IP.swift │ └── main.swift ├── TechnicalOverview.md ├── Tests ├── LinuxMain.swift ├── SEProcessorLibTests │ └── SEProcessirLibTests.swift ├── SEProcessorTests │ └── SEProcessorTests.swift └── SwiftEngineServerTests │ └── SwiftEngineTests.swift ├── bin └── se ├── install.sh └── run.sh /.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 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | Packages/ 37 | Package.pins 38 | Package.resolved 39 | .build/ 40 | 41 | *.xcodeproj/ 42 | 43 | # CocoaPods 44 | # 45 | # We recommend against adding the Pods directory to your .gitignore. However 46 | # you should judge for yourself, the pros and cons are mentioned at: 47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 48 | # 49 | # Pods/ 50 | 51 | # Carthage 52 | # 53 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 54 | # Carthage/Checkouts 55 | 56 | Carthage/Build 57 | 58 | # fastlane 59 | # 60 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 61 | # screenshots whenever they are needed. 62 | # For more information about the recommended setup visit: 63 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 64 | 65 | fastlane/report.xml 66 | fastlane/Preview.html 67 | fastlane/screenshots/**/*.png 68 | fastlane/test_output 69 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at conduct@swiftengine.io. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thank you for visiting our repository and for your interest in helping improve SwiftEngine! We appreciate feedback of any kind, the only thing we asking of you is that you follow our [Code of Conduct](#code-of-conduct). 4 | 5 | Please choose the option below that fits you best: 6 | 7 | ### How to Contribute 8 | 9 | The `master` branch will always contain the most stable release, while `dev` will contain features currently in progress. Be sure to comment your code appropriately. 10 | 11 | Please follow the steps below to contribute: 12 | 13 | - Fork our repo 14 | - Create a feature branch off the `dev` branch titled `dev-xxx` where `xxx` is the feature you are adding or fixing 15 | - Make your awesome change(s) 16 | - Commit your code with a descriptive message 17 | - Submit a pull request against the `dev` branch 18 | - Done! 19 | 20 | ### Submit a Bug Report 21 | 22 | Is something in SwiftEngine not working as intended? Please open an issue and include the following: 23 | 24 | - A couple sentences generally describing the issue you encountered 25 | - The _simplest_ possible steps we need to take to reproduce this issue 26 | - Your operating system by running the command `uname -a` in a terminal 27 | - Anything else you think might aid us in resolving the issue 28 | 29 | You may use the following example as a template to report an issue: 30 | 31 | ``` 32 | Context: 33 | My server logs are not rotating properly after I have specified a max log size and that size is exceeded. 34 | 35 | Steps to Reproduce: 36 | 1. ... 37 | 2. ... 38 | 3. ... 39 | 40 | $ uname -a 41 | Darwin Kernel Version 17.7.0: Thu Jun 21 22:53:14 PDT 2018; root:xnu-4570.71.2~1/RELEASE_X86_64 x86_64 42 | ``` 43 | 44 | 45 | 46 | We thank you for taking the time to help improve SwiftEngine! 47 | 48 | -------------------------------------------------------------------------------- /Extra/SwiftEngineCore/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | -------------------------------------------------------------------------------- /Extra/SwiftEngineCore/Makefile: -------------------------------------------------------------------------------- 1 | swift_version = 4.1.3 2 | swift = /opt/apple/swift-latest/usr/bin/swift 3 | runner = $(shell whoami) 4 | pwd = $(shell pwd) 5 | build_dir = $(pwd)/.build 6 | release_dir = $(build_dir)/release 7 | SECORE_LOCATION = $(release_dir) 8 | 9 | default: build 10 | 11 | build: 12 | $(swift) build --product SwiftEngine -c release -Xswiftc -g 13 | rm -f $(SECORE_LOCATION)/SEObjects.list 14 | (cd $(release_dir) && find . -type f \( -name "*.o" ! -iname "main.swift.o" \) >> $(SECORE_LOCATION)/SEObjects.list ) 15 | rm -f $(SECORE_LOCATION)/SEmodulemaps.list 16 | (cd $(release_dir) && find . -type f -name "*.modulemap" >> $(SECORE_LOCATION)/SEmodulemaps.list ) 17 | 18 | package: 19 | 20 | install: 21 | 22 | clean: 23 | swift package clean 24 | rm Package.resolved -------------------------------------------------------------------------------- /Extra/SwiftEngineCore/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.0 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "SwiftEngine", 8 | products: [ 9 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 10 | .library( 11 | name: "SwiftEngine", 12 | type: .dynamic, 13 | //type: .static, 14 | targets: ["SwiftEngine"]), 15 | ], 16 | dependencies: [ 17 | // Dependencies declare other packages that this package depends on. 18 | .package(url: "https://github.com/IBM-Swift/SwiftyJSON", from: "17.0.2"), 19 | ], 20 | targets: [ 21 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 22 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 23 | .target( 24 | name: "SwiftEngine", 25 | dependencies: [ 26 | "SwiftyJSON", 27 | // "MongoKitten"//, "ExtendedJSON" 28 | ], 29 | path: "Sources/SwiftEngineCore" 30 | ), 31 | .testTarget( 32 | name: "SwiftEngineTest", 33 | dependencies: ["SwiftEngine"], 34 | path: "Tests/SwiftEngineCoreTests"), 35 | ] 36 | ) 37 | -------------------------------------------------------------------------------- /Extra/SwiftEngineCore/README.md: -------------------------------------------------------------------------------- 1 | # SwiftEngineCore 2 | 3 | A description of this package. 4 | -------------------------------------------------------------------------------- /Extra/SwiftEngineCore/Sources/SwiftEngineCore/Request.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SwiftyJSON 3 | 4 | 5 | public typealias RouteParams = [String : Any] 6 | 7 | public class Request { 8 | 9 | unowned let ctx : RequestContext 10 | 11 | public var headers = CIDictionary() 12 | public var server = CIDictionary() 13 | 14 | lazy public var body : RequestBody? = { 15 | if let requestId = self.server["REQUEST_ID"], 16 | let fh = FileHandle(forReadingAtPath: "/tmp/\(requestId)") 17 | { 18 | let data = fh.readDataToEndOfFile() 19 | return RequestBody(data:data) 20 | } 21 | return nil 22 | }() 23 | 24 | public init(ctx: RequestContext) { 25 | self.ctx = ctx 26 | 27 | let environment = ProcessInfo.processInfo.environment 28 | for (key, val) in environment { 29 | 30 | if key.starts(with: "HTTP_") { 31 | self.headers[String(key.dropFirst(5)).replacingOccurrences(of: "_", with: "-").capitalized] = val 32 | } 33 | else { 34 | self.server[key] = val 35 | } 36 | } 37 | } 38 | } 39 | 40 | 41 | 42 | public typealias CIDictionary = Dictionary 43 | 44 | 45 | public extension CIDictionary where Key == String { 46 | 47 | subscript(caseInsensitive key: Key) -> Value? { 48 | get { 49 | if let k = keys.first(where: { $0.caseInsensitiveCompare(key) == .orderedSame }) { 50 | return self[k] 51 | } 52 | return nil 53 | } 54 | set { 55 | if let k = keys.first(where: { $0.caseInsensitiveCompare(key) == .orderedSame }) { 56 | self[k] = newValue 57 | } else { 58 | self[key] = newValue 59 | } 60 | } 61 | } 62 | } 63 | 64 | 65 | public class RequestBody { 66 | public let data : Data 67 | 68 | lazy private(set) public var string : String? = { 69 | return String(data: self.data, encoding: String.Encoding.utf8) 70 | }() 71 | 72 | lazy private(set) public var json : JSON? = { 73 | return JSON(data: self.data) 74 | }() 75 | 76 | init(data: Data){ 77 | self.data = data 78 | } 79 | 80 | 81 | 82 | } 83 | 84 | -------------------------------------------------------------------------------- /Extra/SwiftEngineCore/Sources/SwiftEngineCore/RequestContext.swift: -------------------------------------------------------------------------------- 1 | 2 | 3 | open class RequestContext { 4 | 5 | lazy private (set) public var request: Request = Request(ctx: self) 6 | lazy private (set) public var response: Response = Response(ctx: self) 7 | public var requestHandlers = [RequestHandler]() 8 | 9 | 10 | public init() { 11 | 12 | } 13 | 14 | // url route format users/:userid/books 15 | // this is the designated initializer method for the handler 16 | public func addHandler(forMethod method:String = "*", withRoute route:String = "*", handler: @escaping (Request, Response)->() ) { 17 | 18 | guard !self.response.httpBodyResponseStarted else { 19 | //self.response.stderror("Error: Cannot add response handlers after response output has begun", withExitCode: -200) 20 | return 21 | } 22 | 23 | // restrict the use of the response so it cannot be called outside of request handlers 24 | self.response.responseRestrictedByRequestHandlers = true 25 | 26 | let requestHandler = RequestHandler(request: self.request, method:method, route:route, handler:handler) 27 | self.requestHandlers.append(requestHandler) 28 | 29 | } 30 | 31 | // this is a convenience method in case the closer doesnt care about the paramenters 32 | // public func addHandler(forMethod method:String = "*", withRoute route:String = "*", handler: @escaping (Request, Response)->() ) { 33 | // let __handler : (Request, Response)->() = { 34 | // req, res in 35 | // handler(req, res) 36 | // } 37 | // addHandler(forMethod:method, withRoute:route, handler:__handler) 38 | // } 39 | 40 | public func execValidHandler() { 41 | // clear the response buffer and unrestrict the use of the response library 42 | self.response.responseBuffer = "" 43 | self.response.responseRestrictedByRequestHandlers = false 44 | 45 | let validHandlers = self.getValidHandlers() 46 | 47 | if (validHandlers.count > 0){ 48 | // now call the request 49 | let requestHandler = validHandlers.first! 50 | requestHandler.handler(self.request, self.response) 51 | return 52 | } 53 | 54 | // if we do have request handlers but nothing matched 55 | if(requestHandlers.count > 0 && validHandlers.count == 0){ 56 | //self.response.stderror("Error: no matching request handler defined for this type of request", withExitCode: -200) 57 | return 58 | } 59 | } 60 | 61 | private func getValidHandlers() -> [RequestHandler] { 62 | let filteredHandlers = requestHandlers.filter{ 63 | (requestHandler: RequestHandler) -> Bool in 64 | return requestHandler.isValid() 65 | } 66 | 67 | return filteredHandlers 68 | } 69 | 70 | 71 | } 72 | 73 | -------------------------------------------------------------------------------- /Extra/SwiftEngineCore/Sources/SwiftEngineCore/RequestHandler.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | 4 | 5 | open class RequestHandler { 6 | 7 | public let method : String 8 | public let route : String 9 | public let handler : (Request, Response) -> () 10 | private let request: Request // doing this for dependency injection compatibility 11 | public var routeParams = RouteParams() 12 | 13 | init(request: Request, method:String, route:String, handler: @escaping (Request, Response)->()){ 14 | self.request = request 15 | self.method = method 16 | self.route = route 17 | self.handler = handler 18 | } 19 | 20 | open func get(_ endpoint: String, _ handler: @escaping (Request.Type, Response.Type) -> ()) { 21 | handler(Request.self, Response.self) 22 | } 23 | 24 | func isValid() -> Bool { 25 | if (!isMethodValid()) { 26 | return false 27 | } 28 | if (!validateRoute()) { 29 | return false 30 | } 31 | return true 32 | } 33 | 34 | 35 | func validateRoute() -> Bool { 36 | // first let's split up the current requesr URI 37 | var requestRoute = self.request.server["REQUEST_URI"]! 38 | 39 | // When request have value like 'https://www.abc.com/school?size=5' 40 | // the validateRoute will return false 41 | // So to return true we have to remove '?size=5' 42 | let charset = CharacterSet(charactersIn: "?") 43 | if requestRoute.rangeOfCharacter(from: charset) != nil { 44 | requestRoute = requestRoute.components(separatedBy: "?")[0] 45 | } 46 | 47 | let requestRouteArr = requestRoute.components(separatedBy: "/") 48 | 49 | // now let's split up our own route 50 | let handlerRouteArr = self.route.components(separatedBy: "/") 51 | 52 | // if the handlerRoute has more parts than the request route then this is no good 53 | if handlerRouteArr.count > requestRouteArr.count { 54 | return false 55 | } 56 | 57 | 58 | var routeParamDict = RouteParams() 59 | 60 | // now loop through our array and make sure we match 61 | for i in 0.. 0 && handlerRouteComponent.first! == ":"){ 78 | 79 | var paramName = handlerRouteComponent // get the parameter name 80 | paramName.remove(at: paramName.startIndex) // remove the collons mark from the parameter name 81 | 82 | routeParamDict[paramName] = requestRouteComponent 83 | 84 | continue 85 | } 86 | 87 | // finally if we got here, then the route needs to match 88 | if(requestRouteComponent.lowercased() != handlerRouteComponent.lowercased()){ 89 | return false 90 | } 91 | 92 | } 93 | 94 | // now assign the route parameters if we got some 95 | self.routeParams = routeParamDict 96 | 97 | return true 98 | } 99 | 100 | func isMethodValid() -> Bool { 101 | 102 | let reqMethod = request.server["REQUEST_METHOD"] ?? "" 103 | 104 | // if this is not a catch all, and does not match the current request method 105 | if (self.method != "*" && self.method != reqMethod) { 106 | return false 107 | } 108 | 109 | return true 110 | 111 | } 112 | 113 | } 114 | 115 | -------------------------------------------------------------------------------- /Extra/SwiftEngineCore/Sources/SwiftEngineCore/Response.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | 4 | 5 | public class Response { 6 | 7 | let stdin = FileHandle.standardInput 8 | let stdout = FileHandle.standardOutput 9 | let stderror = FileHandle.standardError 10 | 11 | var httpBodyResponseStarted = false 12 | var responseRestrictedByRequestHandlers = false 13 | var responseBuffer: String? 14 | 15 | unowned let ctx : RequestContext 16 | 17 | init(ctx: RequestContext) { 18 | self.ctx = ctx 19 | } 20 | 21 | 22 | @discardableResult 23 | public func write(_ string: String) -> Response { 24 | 25 | // if this is just whitespace including the newlines then buffer it up 26 | // this helps avoid accidental errors in case the use has extra spaces from their html tags, especially as it pertains to writting headrs 27 | if string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { 28 | self.responseBuffer = (self.responseBuffer ?? "") + string 29 | return self.ctx.response 30 | } 31 | 32 | if (responseRestrictedByRequestHandlers) { 33 | self.responseErr("Error: When using request handlers, response may only be initiated by the request handler function calls ", withExitCode: -200) 34 | return self.ctx.response 35 | } 36 | 37 | if (!httpBodyResponseStarted) { 38 | self.responseOut("HTTP/1.1 200 OK\n") 39 | self.responseOut("\n") // this is important; we follow the HTTP protocol standard here for separating header data from body 40 | httpBodyResponseStarted = true 41 | } 42 | 43 | 44 | // if we have something buffered up then now is the time to flush it 45 | if let responseBuffer = self.responseBuffer { 46 | self.responseOut(responseBuffer) 47 | self.responseBuffer = nil 48 | } 49 | 50 | //guard let data = string.data(using: .utf8) else { return } 51 | //stdout.write(data) 52 | 53 | self.responseOut(string) 54 | 55 | return self.ctx.response 56 | } 57 | 58 | 59 | @discardableResult 60 | public func header(_ value: String) -> Response { 61 | guard !httpBodyResponseStarted else { 62 | self.responseErr("Error: Cannot write header data '\(value)' after response output has begun", withExitCode: -200) 63 | return self.ctx.response 64 | } 65 | 66 | // make sure at least one of the parameters contains a value 67 | guard !(value.isEmpty) else{ 68 | return self.ctx.response 69 | } 70 | 71 | self.responseOut("\(value)\r\n") 72 | return self.ctx.response 73 | } 74 | 75 | private func responseOut(_ str: String) { 76 | guard let data = str.data(using: .utf8) else { return } 77 | self.stdout.write(data) 78 | } 79 | 80 | private func responseErr(_ string: String, withExitCode exitCode: Int32? = nil){ 81 | guard let data = string.data(using: .utf8) else { return } 82 | self.stderror.write(data) 83 | if let exitCode = exitCode { 84 | exit(exitCode) 85 | } 86 | } 87 | 88 | 89 | private func statusMessage(forCode code: Int) -> String { 90 | switch code { 91 | case 100: return "Continue" 92 | case 101: return "Switching Protocols" 93 | case 200: return "OK" 94 | case 201: return "Created" 95 | case 202: return "Accepted" 96 | case 203: return "Non-Authoritative Information" 97 | case 204: return "No Content" 98 | case 205: return "Reset Content" 99 | case 206: return "Partial Content" 100 | case 300: return "Multiple Choices" 101 | case 301: return "Moved Permanently" 102 | case 302: return "Moved Temporarily" 103 | case 303: return "See Other" 104 | case 304: return "Not Modified" 105 | case 305: return "Use Proxy" 106 | case 400: return "Bad Request" 107 | case 401: return "Unauthorized" 108 | case 402: return "Payment Required" 109 | case 403: return "Forbidden" 110 | case 404: return "Not Found" 111 | case 405: return "Method Not Allowed" 112 | case 406: return "Not Acceptable" 113 | case 407: return "Proxy Authentication Required" 114 | case 408: return "Request Time-out" 115 | case 409: return "Conflict" 116 | case 410: return "Gone" 117 | case 411: return "Length Required" 118 | case 412: return "Precondition Failed" 119 | case 413: return "Request Entity Too Large" 120 | case 414: return "Request-URI Too Large" 121 | case 415: return "Unsupported Media Type" 122 | case 500: return "Internal Server Error" 123 | case 501: return "Not Implemented" 124 | case 502: return "Bad Gateway" 125 | case 503: return "Service Unavailable" 126 | case 504: return "Gateway Time-out" 127 | case 505: return "HTTP Version not supported" 128 | default: return "Unknown http status code \(code)" 129 | } 130 | } 131 | 132 | } 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /Extra/SwiftEngineCore/Sources/SwiftEngineCore/SwiftEngineCore.swift: -------------------------------------------------------------------------------- 1 | struct SwiftEngineCore { 2 | var text = "Hello, World!" 3 | } 4 | -------------------------------------------------------------------------------- /Extra/SwiftEngineCore/Sources/SwiftEngineCore/UploadedFile.swift: -------------------------------------------------------------------------------- 1 | // File.swift 2 | import Foundation 3 | 4 | 5 | public class UploadedFile{ 6 | public var name : String? 7 | public var tmpPath : String? 8 | public var type : String? 9 | public var error : Int = 0 10 | public var size : Int = -1 11 | public var isPublic : Bool = false { 12 | willSet { 13 | if(newValue){ 14 | shell("chmod","a+r", self.tmpPath!) 15 | }else{ 16 | shell("chmod","a-r", self.tmpPath!) 17 | } 18 | 19 | } 20 | } 21 | 22 | 23 | public func moveTo(path: String) throws{ 24 | 25 | let fileManager = FileManager.default 26 | 27 | if let tmpPath = self.tmpPath{ 28 | 29 | if(fileManager.fileExists(atPath:path)){ 30 | try fileManager.removeItem(atPath:path) 31 | } 32 | 33 | try fileManager.moveItem(atPath: tmpPath, toPath: path) 34 | 35 | self.tmpPath = path // set the new temp path so future refrence is from here 36 | 37 | } 38 | 39 | } 40 | 41 | 42 | 43 | } 44 | 45 | 46 | @discardableResult 47 | public func shell(_ args: String...) -> Int32 { 48 | 49 | //let fm = FileManager.default 50 | //let pwd = fm.currentDirectoryPath 51 | 52 | #if swift(>=3.1) 53 | let task = Process() 54 | #else 55 | let task = Task() 56 | #endif 57 | task.launchPath = "/usr/bin/env" 58 | task.arguments = args 59 | task.launch() 60 | task.waitUntilExit() 61 | return task.terminationStatus 62 | } 63 | -------------------------------------------------------------------------------- /Extra/SwiftEngineCore/Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import SwiftEngineCoreTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += SwiftEngineCoreTests.allTests() 7 | XCTMain(tests) -------------------------------------------------------------------------------- /Extra/SwiftEngineCore/Tests/SwiftEngineCoreTests/SwiftEngineCoreTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import SwiftEngineCore 3 | 4 | final class SwiftEngineCoreTests: XCTestCase { 5 | func testExample() { 6 | // This is an example of a functional test case. 7 | // Use XCTAssert and related functions to verify your tests produce the correct 8 | // results. 9 | XCTAssertEqual(SwiftEngineCore().text, "Hello, World!") 10 | } 11 | 12 | 13 | static var allTests = [ 14 | ("testExample", testExample), 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /Extra/SwiftEngineCore/Tests/SwiftEngineCoreTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !os(macOS) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(SwiftEngineCoreTests.allTests), 7 | ] 8 | } 9 | #endif -------------------------------------------------------------------------------- /Extra/templates/etc/swiftengine/magic/main.swift: -------------------------------------------------------------------------------- 1 | import SwiftEngine 2 | 3 | let ctx = RequestContext() 4 | entryPoint(ctx: ctx) 5 | ctx.execValidHandler() -------------------------------------------------------------------------------- /Extra/templates/var/swiftengine/www/common.swift: -------------------------------------------------------------------------------- 1 | // Import SwiftEngine essentials 2 | import SwiftEngine 3 | 4 | class Common{ 5 | 6 | //get a reference to RequestContext 7 | // read more at: http://kb.swiftengine.io/RequestContext 8 | class func printContextInfo(for ctx: RequestContext){ 9 | 10 | // get refrences to request and response object of current context 11 | // read more at: http://kb.swiftengine.io/RequestContext 12 | let req = ctx.request 13 | let res = ctx.response 14 | 15 | // retrieve some server side variables 16 | // read more at: http://kb.swiftengine.io/Request 17 | let clientIP = req.server["REMOTE_ADDR"] 18 | 19 | // write out some data 20 | // read more at: http://kb.swiftengine.io/Response 21 | if let clientIP = clientIP { 22 | res.write("Your IP is: \(clientIP)") 23 | } 24 | 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /Extra/templates/var/swiftengine/www/default.swift: -------------------------------------------------------------------------------- 1 | // Import SwiftEngine essentials 2 | // read more at: https://kb.swiftengine.io/SwiftEnginAnatomy 3 | import SwiftEngine 4 | 5 | 6 | // specify other required files for this file 7 | // read more at: https://kb.swiftengine.io/requires 8 | 9 | //se: require /common.swift 10 | 11 | // Entry Point function; where all code begins 12 | // read more at: https://kb.swiftengine.io/entryPoint 13 | func entryPoint(ctx: RequestContext) { 14 | 15 | // add GET handlers to the request context 16 | // read more at: https://kb.swiftengine.io/requestHandlers 17 | ctx.addHandler(forMethod:"GET", withRoute:"*"){ 18 | req, res in 19 | res.write("Hello from SwiftEngine! ") 20 | Common.printContextInfo(for: ctx) 21 | } 22 | 23 | // add POST handlers to the request context 24 | // read more at: https://kb.swiftengine.io/requestHandlers 25 | ctx.addHandler(forMethod:"POST", withRoute:"*"){ 26 | req, res in 27 | res.write("Handle for POST request method") 28 | } 29 | 30 | // add catch-all handlers to the request context 31 | // read more at: https://kb.swiftengine.io/requestHandlers 32 | ctx.addHandler(forMethod:"*", withRoute:"*"){ 33 | req, res in 34 | res.write("Handle for catch-all") 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | swift_version = 4.1.3 2 | UNAME = $(shell uname) 3 | TMP_DIR = /tmp 4 | swift = /opt/apple/swift-latest/usr/bin/swift 5 | runner = $(shell whoami) 6 | pwd = $(shell pwd) 7 | build_dir = $(pwd)/.build 8 | release_dir = $(build_dir)/debug 9 | SECORE_LOCATION = $(pwd)/Extra/SwiftEngineCore/.build/release 10 | 11 | default: build 12 | 13 | build: build-swiftengineserver build-seprocessor build-secore 14 | 15 | run: build 16 | SEPROCESSOR_LOCATION=$(release_dir)/SEProcessor \ 17 | SECORE_LOCATION=$(SECORE_LOCATION) \ 18 | $(swift) run SwiftEngineServer 19 | 20 | 21 | build-swiftengineserver: 22 | $(swift) build --product SwiftEngineServer -Xswiftc -g 23 | 24 | build-seprocessor: 25 | $(swift) build --product SEProcessor -Xswiftc -g 26 | 27 | build-secore: 28 | make -C Extra/SwiftEngineCore build 29 | #$(swift) build --product SwiftEngine -c release -Xswiftc -g 30 | #rm -f $(SECORE_LOCATION)/SEObjects.list 31 | #find $(release_dir)/SwiftEngine.build -type f \( -name "*.o" ! -iname "main.swift.o" \) -exec basename {} \; >> $(SECORE_LOCATION)/SEObjects.list 32 | #rm -f $(SECORE_LOCATION)/SEmodulemaps.list 33 | #find $(release_dir)/SwiftEngine.build -type f -name "*.modulemap" -exec basename {} \; >> $(SECORE_LOCATION)/SEmodulemaps.list 34 | #chown -R www-data:www-data . 35 | #find .build/release -type d -name "*.build" -exec chmod -R a+x {} \; 36 | #find .build -type d -exec chmod -R a+x {} \; 37 | 38 | #./.build/x86_64-apple-macosx10.10/release/libSwiftEngine.dylib ?? hot to use this 39 | 40 | build-swiftengine-releasepack: 41 | rm $(build_dir)/releasePack.zip 42 | find $(release_dir) -name "*.swiftmodule" -exec zip releasePack.zip {} + 43 | find $(build_dir)/checkouts -name "*.h" -exec zip releasePack.zip {} + 44 | find $(release_dir)/SwiftEngine.build -name "*.o" -exec zip releasePack.zip {} + 45 | find $(build_dir) -name "*.modulemap" -exec zip releasePack.zip {} + 46 | zip releasePack.zip $(SECORE_LOCATION)/SEObjects.list + 47 | zip releasePack.zip $(SECORE_LOCATION)/SEmodulemaps.list + 48 | 49 | 50 | install-deps: install-dependencies 51 | 52 | install-dependencies: 53 | ifneq ($(runner), root) 54 | @echo "Must run as root user" 55 | else 56 | ifeq ($(UNAME), Linux) 57 | ifeq ($(shell test "$(shell lsb_release -r -s)" = 14.04 -o \ 58 | "$(shell lsb_release -r -s)" = 16.04 -o \ 59 | "$(shell lsb_release -r -s)" = 16.10 && printf "true"),true) 60 | make install-dependencies-linux 61 | make setup-system 62 | else 63 | @echo This version of Linux is not currently supported, please use Ubuntu 14.04, 16.04 or 16.10 64 | endif 65 | else ifeq ($(UNAME), Darwin) 66 | make install-dependencies-mac 67 | make setup-system 68 | else 69 | @echo "$(UNAME) platform is not currently supported" 70 | endif 71 | endif 72 | 73 | 74 | install-dependencies-linux: 75 | $(eval ubuntu_version = $(shell lsb_release -r -s)) 76 | $(eval short_ubuntu_version = $(shell echo $(ubuntu_version) | tr --delete .)) 77 | $(eval swift_download_source = "https://swift.org/builds/swift-$(swift_version)-release/ubuntu$(short_ubuntu_version)/swift-$(swift_version)-RELEASE/swift-$(swift_version)-RELEASE-ubuntu$(ubuntu_version).tar.gz" ) 78 | make install-cleanup-ubuntu 79 | apt-get -y install git cmake ninja-build clang python uuid-dev libicu-dev icu-devtools libbsd-dev libedit-dev libxml2-dev libsqlite3-dev swig libpython-dev libncurses5-dev pkg-config libblocksruntime-dev libcurl4-openssl-dev systemtap-sdt-dev tzdata rsync 80 | apt-get -y install libz-dev 81 | wget $(swift_download_source) -O $(TMP_DIR)/swift-$(swift_version)-RELEASE-ubuntu.tar.gz 82 | mkdir -p $(TMP_DIR)/swift-$(swift_version)-RELEASE-ubuntu 83 | tar -xvzf $(TMP_DIR)/swift-$(swift_version)-RELEASE-ubuntu.tar.gz --directory $(TMP_DIR)/swift-$(swift_version)-RELEASE-ubuntu --strip-components=1 84 | rm -rf /opt/apple/swift-$(swift_version) 85 | mkdir -p /opt/apple/swift-$(swift_version) 86 | mv $(TMP_DIR)/swift-$(swift_version)-RELEASE-ubuntu/usr /opt/apple/swift-$(swift_version)/usr 87 | ln -s /opt/apple/swift-$(swift_version) /opt/apple/swift-latest 88 | make install-cleanup-ubuntu 89 | 90 | install-dependencies-mac: 91 | make install-cleanup-mac 92 | curl https://swift.org/builds/swift-$(swift_version)-release/xcode/swift-$(swift_version)-RELEASE/swift-$(swift_version)-RELEASE-osx.pkg --output $(TMP_DIR)/swift-$(swift_version)-RELEASE-osx.pkg 93 | pkgutil --expand $(TMP_DIR)/swift-$(swift_version)-RELEASE-osx.pkg $(TMP_DIR)/swift-$(swift_version)-RELEASE-osx.unpkg 94 | mkdir -p $(TMP_DIR)/swift-$(swift_version)-RELEASE-osx 95 | (cd $(TMP_DIR)/swift-$(swift_version)-RELEASE-osx && ( cat ../swift-$(swift_version)-RELEASE-osx.unpkg/swift-$(swift_version)-RELEASE-osx-package.pkg/Payload | gzip -d | cpio -id )) 96 | rm -rf /opt/apple/swift-$(swift_version) 97 | mkdir -p /opt/apple/swift-$(swift_version) 98 | mv $(TMP_DIR)/swift-$(swift_version)-RELEASE-osx/usr /opt/apple/swift-$(swift_version)/usr 99 | mv $(TMP_DIR)/swift-$(swift_version)-RELEASE-osx/system /opt/apple/swift-$(swift_version)/system 100 | mv $(TMP_DIR)/swift-$(swift_version)-RELEASE-osx/Developer /opt/apple/swift-$(swift_version)/Developer 101 | ln -s /opt/apple/swift-$(swift_version) /opt/apple/swift-latest 102 | make install-cleanup-mac 103 | 104 | install-cleanup-mac: 105 | rm -rf $(TMP_DIR)/swift-$(swift_version)-RELEASE-osx.pkg 106 | rm -rf $(TMP_DIR)/swift-$(swift_version)-RELEASE-osx.unpkg 107 | rm -rf $(TMP_DIR)/swift-$(swift_version)-RELEASE-osx 108 | 109 | install-cleanup-ubuntu: 110 | rm -rf $(TMP_DIR)/swift-$(swift_version)-RELEASE-ubuntu.tar.gz 111 | rm -rf $(TMP_DIR)/swift-$(swift_version)-RELEASE-ubuntu 112 | 113 | setup-system: 114 | mkdir -p /etc/swiftengine 115 | cp -R Extra/templates/etc/swiftengine/* /etc/swiftengine/ 116 | chmod -R a+w /etc/swiftengine 117 | mkdir -p /var/swiftengine/www /var/swiftengine/.cache 118 | cp -R Extra/templates/var/swiftengine/www/* /var/swiftengine/www/ 119 | chmod -R a+w /var/swiftengine 120 | mkdir -p /var/log/swiftengine 121 | chmod -R a+w /var/log/swiftengine 122 | 123 | 124 | clean: 125 | swift package clean 126 | rm Package.resolved 127 | make -C Extra/SwiftEngineCore clean -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "swift-nio", 6 | "repositoryURL": "https://github.com/apple/swift-nio.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "c6acf77fc9a310c0ebf8d5f73f285d1639801d2f", 10 | "version": "1.9.0" 11 | } 12 | }, 13 | { 14 | "package": "swift-nio-zlib-support", 15 | "repositoryURL": "https://github.com/apple/swift-nio-zlib-support.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "37760e9a52030bb9011972c5213c3350fa9d41fd", 19 | "version": "1.0.0" 20 | } 21 | } 22 | ] 23 | }, 24 | "version": 1 25 | } 26 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.0 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "SwiftEngine", 8 | products: [ 9 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 10 | .executable( 11 | name: "SwiftEngineServer", 12 | targets: ["SwiftEngineServer"]), 13 | .executable( 14 | name: "SEProcessor", 15 | targets: ["SEProcessor"]), 16 | ], 17 | dependencies: [ 18 | // Dependencies declare other packages that this package depends on. 19 | .package(url: "https://github.com/apple/swift-nio.git", from: "1.7.3"), 20 | ], 21 | targets: [ 22 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 23 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 24 | .target( 25 | name: "SwiftEngineServer", 26 | dependencies: ["NIO", "NIOHTTP1", "NIOFoundationCompat"]), 27 | .target( 28 | name: "SEProcessor", 29 | dependencies: ["SEProcessorLib"]), 30 | .target( 31 | name: "SEProcessorLib", 32 | dependencies: []), 33 | 34 | .testTarget( 35 | name: "SwiftEngineServerTests", 36 | dependencies: ["SwiftEngineServer"]), 37 | .testTarget( 38 | name: "SEProcessorLibTests", 39 | dependencies: ["SwiftEngineServer"]), 40 | .testTarget( 41 | name: "SEProcessorTests", 42 | dependencies: ["SwiftEngineServer"]), 43 | ] 44 | ) 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![SwiftEngine](https://i.imgur.com/X6hLAmn.jpg)](https://swiftengine.io) 2 | 3 | ### _We work for :star2:’s and :fork_and_knife:’s ... please Star our repo_ 4 | 5 | :raising_hand: __Hey there!__ 6 | Please note: This project is still in Alpha Version and we’re actively working on forking over a lot of nifty features from SwiftEngine to make it compatible with SwiftNIO. We appreciate any contributors or testers joining our project! 7 | 8 | ## Features :heart_eyes: 9 | 10 | * ___Swift on Back-End___ - Improve productivity by using the modern Swift language for all your app's development needs ([learn more](/TechnicalOverview.md)) 11 | * ___Hot Code Reload___ - Increase the speed of your endpoints as each file is individually compiled. If a file has not been modified since it was last used, it won't need to be recompiled ([learn more](/TechnicalOverview.md)) 12 | * ___Automated Routing Logic___ - Avoid writing custom routers; SwiftEngine will automagically route each request to the desired file ([learn more](/TechnicalOverview.md)) 13 | * ___Uptime Resiliency___ - Reduce risk by leveraging a fail-safe and high-availability operating environment where each client requests functions independently ([learn more](/TechnicalOverview.md)) 14 | * ___Easy web based run-time error analysis___ - Save time by not having to dig through shell dumps; SwiftEngine displays the full error trace on your browser for easy debugging ([learn more](/TechnicalOverview.md)) 15 | 16 | 17 | ## Getting Started :boom: 18 | These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. See deployment for notes on how to deploy the project on a live system. 19 | 20 | ## Prerequisites :exclamation: 21 | What are the dependencies we need to get this to work? 22 | 23 | OS | Version 24 | ------------- | ------------- 25 | macOS | 10.13+ 26 | Ubuntu | 14.04, 16.04, 16.10 27 | 28 | ## Getting started with the project :thumbsup: 29 | 30 | 1. Clone this repo: `git clone https://github.com/swiftengine/SwiftEngine.git` 31 | 2. `cd` to `SwiftEngine` directory and run `sudo ./install.sh` 32 | 3. Run `./run.sh` 33 | This should start the server running and listening on port `8887` 34 | 35 | ## Using :star: 36 | 37 | Programming your site: 38 | 1. Create a new `mypage.swift` file within the `/var/swiftengine/www/` directory (see example below) 39 | 2. From the browser, enter the following url `http://:8887/mypage` (by default this is `localhost:8887`) 40 | 41 | That's it! No compilation or dealing with shell dumps required! Your requested endpoints will be automagically compiled during the first request, and results will be shown. 42 | 43 | Any swift file you place in `/var/swiftengine/www` will be accessible through the browser without the `.swift` extension. 44 | Read more about SwiftEngine's autonomous system and routing logic ([here](/TechnicalOverview.md)) 45 | 46 | ## Example of a SwiftEngine based `.swift` file :trophy: 47 | 48 | ```swift 49 | // Import SwiftEngine essentials 50 | import SwiftEngine 51 | 52 | 53 | // specify other required files for this file 54 | //se: require /common.swift 55 | 56 | // Entry Point function; where all code begins 57 | func entryPoint(ctx: RequestContext) { 58 | 59 | // add GET handlers to the request context 60 | ctx.addHandler(forMethod:"GET", withRoute:"*"){ 61 | req, res in 62 | res.write("Hello from SwiftEngine!") 63 | } 64 | 65 | // add POST handlers to the request context 66 | ctx.addHandler(forMethod:"POST", withRoute:"*"){ 67 | req, res in 68 | res.write("Handle for POST request method") 69 | } 70 | 71 | // add catch-all handlers to the request context 72 | ctx.addHandler(forMethod:"*", withRoute:"*"){ 73 | req, res in 74 | res.write("Handle for catch-all") 75 | } 76 | 77 | } 78 | ``` 79 | 80 | ## Built With :sunny: 81 | * [**SwiftNIO**](https://github.com/apple/swift-nio) 82 | 83 | 84 | 85 | ## Contributing :family: 86 | We would love to hear your thoughts and feedback about SwiftEngine. If you would like to contribute to our project or have an issue you would like to open, please visit our [CONTRIBUTING](/CONTRIBUTING.md) document for more details. 87 | 88 | ## Authors :factory: 89 | * Spartak Buniatyan - Founder - [SpartakB](https://github.com/spartakb) 90 | * Brandon Holden - Developer - [brandon-holden](https://github.com/brandon-holden) 91 | 92 | ## Contact Us :factory: 93 | * You can reach us at github@swiftengine.io 94 | * For issues surrounding conduct. Email us - conduct@swiftengine.io 95 | 96 | ## License :notes: 97 | 98 | This project is licensed under the Mozilla Public License Version 2.0 - see the [LICENSE](/LICENSE.txt) file for details 99 | -------------------------------------------------------------------------------- /Sources/SEProcessor/main.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SEProcessorLib 3 | 4 | func main(){ 5 | let environment = ProcessInfo.processInfo.environment 6 | 7 | guard let documentRoot = environment["DOCUMENT_ROOT"] else { 8 | print("No document root") 9 | exit(0) 10 | } 11 | guard let fileRequest = environment["SCRIPT_NAME"] else { 12 | print("No script name") 13 | exit(0) 14 | } 15 | SEGlobals.DOCUMENT_ROOT = documentRoot 16 | if let seCoreLocation = getArg("secore-location") { 17 | SEGlobals.SECORE_LOCATION = seCoreLocation 18 | } 19 | 20 | let seRoute = SERoute() 21 | let seResponse = SEResponse() 22 | if seRoute.doesRouterExist() { 23 | 24 | } 25 | else { 26 | let (excutableType, excutePath) = seRoute.getExecutableType(fileRequest) 27 | switch excutableType { 28 | case .swift: 29 | SECompiler.excuteRequest(path: excutePath) 30 | case .staticFile: 31 | seResponse.processStaticFile(path: excutePath) 32 | default: 33 | seResponse.fileNotFound(excutePath) 34 | } 35 | } 36 | } 37 | 38 | func getArg(_ key: String) -> String?{ 39 | for argument in CommandLine.arguments { 40 | switch true { 41 | case argument.hasPrefix("-\(key)="): 42 | let val = argument.components(separatedBy: "=") 43 | if val.count > 1 { 44 | return val[1] 45 | } 46 | default: continue 47 | } 48 | 49 | } 50 | 51 | return nil 52 | } 53 | 54 | 55 | 56 | main() 57 | -------------------------------------------------------------------------------- /Sources/SEProcessorLib/ContentType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentType.swift 3 | // SEProcessorPackageDescription 4 | // 5 | // Created by Lam Tran on 5/29/18. 6 | // 7 | 8 | import Foundation 9 | 10 | // MARK: ContentType 11 | public class ContentType { 12 | public static let shared = ContentType() 13 | 14 | public func getContentType(forExtension ext: String) -> String? { 15 | if let type = mimeTypes[ext] { 16 | return type 17 | } 18 | return nil 19 | } 20 | 21 | let mimeTypes = [ 22 | "html": "text/html", 23 | "htm": "text/html", 24 | "shtml": "text/html", 25 | "css": "text/css", 26 | "xml": "text/xml", 27 | "gif": "image/gif", 28 | "jpeg": "image/jpeg", 29 | "jpg": "image/jpeg", 30 | "js": "application/javascript", 31 | "atom": "application/atom+xml", 32 | "rss": "application/rss+xml", 33 | "mml": "text/mathml", 34 | "txt": "text/plain", 35 | "jad": "text/vnd.sun.j2me.app-descriptor", 36 | "wml": "text/vnd.wap.wml", 37 | "htc": "text/x-component", 38 | "png": "image/png", 39 | "tif": "image/tiff", 40 | "tiff": "image/tiff", 41 | "wbmp": "image/vnd.wap.wbmp", 42 | "ico": "image/x-icon", 43 | "jng": "image/x-jng", 44 | "bmp": "image/x-ms-bmp", 45 | "svg": "image/svg+xml", 46 | "svgz": "image/svg+xml", 47 | "webp": "image/webp", 48 | "woff": "application/font-woff", 49 | "jar": "application/java-archive", 50 | "war": "application/java-archive", 51 | "ear": "application/java-archive", 52 | "json": "application/json", 53 | "hqx": "application/mac-binhex40", 54 | "doc": "application/msword", 55 | "pdf": "application/pdf", 56 | "ps": "application/postscript", 57 | "eps": "application/postscript", 58 | "ai": "application/postscript", 59 | "rtf": "application/rtf", 60 | "m3u8": "application/vnd.apple.mpegurl", 61 | "xls": "application/vnd.ms-excel", 62 | "eot": "application/vnd.ms-fontobject", 63 | "ppt": "application/vnd.ms-powerpoint", 64 | "wmlc": "application/vnd.wap.wmlc", 65 | "kml": "application/vnd.google-earth.kml+xml", 66 | "kmz": "application/vnd.google-earth.kmz", 67 | "7z": "application/x-7z-compressed", 68 | "cco": "application/x-cocoa", 69 | "jardiff": "application/x-java-archive-diff", 70 | "jnlp": "application/x-java-jnlp-file", 71 | "run": "application/x-makeself", 72 | "pl": "application/x-perl", 73 | "pm": "application/x-perl", 74 | "prc": "application/x-pilot", 75 | "pdb": "application/x-pilot", 76 | "rar": "application/x-rar-compressed", 77 | "rpm": "application/x-redhat-package-manager", 78 | "sea": "application/x-sea", 79 | "swf": "application/x-shockwave-flash", 80 | "sit": "application/x-stuffit", 81 | "tcl": "application/x-tcl", 82 | "tk": "application/x-tcl", 83 | "der": "application/x-x509-ca-cert", 84 | "pem": "application/x-x509-ca-cert", 85 | "crt": "application/x-x509-ca-cert", 86 | "xpi": "application/x-xpinstall", 87 | "xhtml": "application/xhtml+xml", 88 | "xspf": "application/xspf+xml", 89 | "zip": "application/zip", 90 | "bin": "application/octet-stream", 91 | "exe": "application/octet-stream", 92 | "dll": "application/octet-stream", 93 | "deb": "application/octet-stream", 94 | "dmg": "application/octet-stream", 95 | "iso": "application/octet-stream", 96 | "img": "application/octet-stream", 97 | "msi": "application/octet-stream", 98 | "msp": "application/octet-stream", 99 | "msm": "application/octet-stream", 100 | "docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", 101 | "xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", 102 | "pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation", 103 | "mid": "audio/midi", 104 | "midi": "audio/midi", 105 | "kar": "audio/midi", 106 | "mp3": "audio/mpeg", 107 | "ogg": "audio/ogg", 108 | "m4a": "audio/x-m4a", 109 | "ra": "audio/x-realaudio", 110 | "3gpp": "video/3gpp", 111 | "3gp": "video/3gpp", 112 | "ts": "video/mp2t", 113 | "mp4": "video/mp4", 114 | "mpeg": "video/mpeg", 115 | "mpg": "video/mpeg", 116 | "mov": "video/quicktime", 117 | "webm": "video/webm", 118 | "flv": "video/x-flv", 119 | "m4v": "video/x-m4v", 120 | "mng": "video/x-mng", 121 | "asx": "video/x-ms-asf", 122 | "asf": "video/x-ms-asf", 123 | "wmv": "video/x-ms-wmv", 124 | "avi": "video/x-msvideo" 125 | ] 126 | } 127 | -------------------------------------------------------------------------------- /Sources/SEProcessorLib/SECommon.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | public class SECommon { 4 | 5 | // Check Exits File 6 | public class func checkExitsFile(filePath path: String) -> Bool { 7 | return FileManager.default.fileExists(atPath: path) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Sources/SEProcessorLib/SECompiler.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public class SECompiler { 4 | 5 | static let swiftc = "/opt/apple/swift-latest/usr/bin/swiftc" 6 | static let swift = "/opt/apple/swift-latest/usr/bin/swift" 7 | 8 | // Location of the main.swift file 9 | static let seMain = "/etc/swiftengine/magic/main.swift" 10 | 11 | 12 | /* 13 | Ex. DOCUMENT_ROOT: /usr/me/ 14 | Request comes in for /usr/me/dir1/dir2/test.swift 15 | 16 | relativePath: dir1/dir2/ 17 | executable: test 18 | */ 19 | static var relativePath: String! 20 | static var executableName: String! 21 | static var seCoreObjectList: String! 22 | static let binaryCompilationLocation = "/var/swiftengine/.cache" 23 | static let entryPointFilename = "default" 24 | 25 | static var fullExecutablePath: String { 26 | return "\(SECompiler.binaryCompilationLocation)\(SECompiler.relativePath!)/\(SECompiler.executableName!)" 27 | } 28 | 29 | static var requireList: Set = [] 30 | 31 | // This method is the *only* way to access SECompiler 32 | public class func excuteRequest(path: String) { 33 | // Set executable name 34 | SECompiler.setPathComponents(forPath: path) 35 | 36 | SECompiler.setSECoreObjectLibrary() 37 | 38 | // Execute request 39 | SECompiler._excuteRequest(path: path) 40 | } 41 | 42 | private class func setSECoreObjectLibrary() { 43 | #if os(OSX) 44 | SECompiler.seCoreObjectList = "\(SEGlobals.SECORE_LOCATION)/libSwiftEngine.dylib" 45 | #else 46 | SECompiler.seCoreObjectList = "\(SEGlobals.SECORE_LOCATION)/libSwiftEngine.so" 47 | #endif 48 | } 49 | 50 | class func dump(_ str: String, _ doExit: Bool = false){ 51 | print(str) 52 | if(doExit){ 53 | exit(0) 54 | } 55 | } 56 | 57 | // Solely for test purposes; remove before deployment 58 | private class func printEnvVars(_ envVars: [String:String]) { 59 | let keys = envVars.keys.sorted() 60 | var startedHttp = false 61 | var finishedHttp = false 62 | var startedServer = false 63 | dump("\nEnv Vars:") 64 | for key in keys { 65 | if !startedHttp && key.starts(with: "HTTP") { 66 | startedHttp = true 67 | dump("") 68 | } 69 | if startedHttp && !finishedHttp && !key.starts(with: "HTTP") { 70 | finishedHttp = true 71 | dump("") 72 | } 73 | else if finishedHttp && !startedServer && key.starts(with: "SERVER") { 74 | startedServer = true 75 | dump("") 76 | } 77 | dump("\(key)=\(envVars[key]!)") 78 | } 79 | dump("", true) 80 | } 81 | 82 | 83 | private class func compileFile(fileUri : String) { 84 | 85 | //dump("Binary Location: \(binaryCompilationLocation)\nRelative Path: \(relativePath!)\nExecutable: \(executableName!)\nFull exe path: \(fullExecutablePath)", true) 86 | 87 | //SECompiler.printEnvVars(ProcessInfo.processInfo.environment) 88 | 89 | var args = [ 90 | SECompiler.swiftc, 91 | "-v", 92 | "-g", 93 | //"-Xcc", "-num-threads", "-Xcc", "25", 94 | "-o", SECompiler.fullExecutablePath, // Compile binary to this location 95 | "-I", "\(SEGlobals.SECORE_LOCATION)", // Add path to SECore for search path 96 | //"-Xcc", "-v", 97 | SECompiler.seMain, // This should always be the first source file so it is treated as the primary file 98 | ] 99 | 100 | 101 | #if os(OSX) 102 | args.append("-sdk") 103 | args.append("/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.13.sdk") 104 | #endif 105 | 106 | do { 107 | let contents = try SECompiler.getFileContents(path: fileUri) 108 | SECompiler.requireList.insert(fileUri) 109 | SECompiler.generateRequireList(path: fileUri, content: contents) 110 | args.append(contentsOf: SECompiler.requireList) 111 | 112 | // Write out .sources file 113 | SECompiler.createSourcesFile(requiresList: SECompiler.requireList) 114 | } 115 | catch { 116 | let errorStr = "

error: invalid file path \(fileUri)

" 117 | SEResponse.outputHTML(status: 500, title: "File Not Found", style: nil, body: errorStr, compilationError: true) 118 | } 119 | 120 | // Add SECore objects 121 | args.append(self.seCoreObjectList!) 122 | 123 | //printEnvVars(ProcessInfo.processInfo.environment) 124 | 125 | //dump(SECompiler.relativePath!) 126 | //dump(SECompiler.fullExecutablePath, true) 127 | 128 | 129 | // let cmd = args.joined(separator: " ") 130 | // print("cmd: \(cmd)") 131 | 132 | // Run the executable 133 | let newArgs = args //["/usr/bin/env"] 134 | let (_, stdErr, status) = SEShell.run(newArgs) 135 | if (status != 0) { 136 | let output = SECompiler.getErrors(stdErr) 137 | SEResponse.outputHTML(status: 500, title: nil, style: SECompiler.lineNumberStyle, body: output, compilationError: true) 138 | } 139 | 140 | 141 | } 142 | 143 | 144 | 145 | // // Get's the necessary SECore objects from the specified path 146 | // private class func getSECoreObjectsList() -> [String] { 147 | // if let contents = try? SECompiler.getFileContents(path: SECompiler.pathToSECoreObjectsList) { 148 | // var ret = [String]() 149 | // var files = contents.components(separatedBy: .newlines) 150 | // files = files.filter(){ $0 != ""} 151 | // ret.append(contentsOf: files) 152 | // return ret 153 | // } 154 | // return [String]() 155 | // } 156 | 157 | 158 | 159 | // DFS-search starting with the entry point file to generate the list of required files 160 | private class func generateRequireList(path: String, content: String) { 161 | // Get lines of the file 162 | let lines = content.components(separatedBy: .newlines) 163 | for (lineNum, line) in lines.enumerated() { 164 | // Starts with the require key 165 | if line.starts(with: SEGlobals.REQUIRE_KEY) { 166 | 167 | // Split components to get the require file name 168 | let lineComponents = line.components(separatedBy: SEGlobals.REQUIRE_KEY) 169 | for file in lineComponents { 170 | // File isn't empty and it's not in the require list 171 | if (!file.isEmpty) && (!SECompiler.requireList.contains(file)) { 172 | 173 | // If the require starts with '/' then path is DOCUMENT_ROOT; else, it's down the full path 174 | var requirePath = "\(SEGlobals.DOCUMENT_ROOT)" 175 | if !file.starts(with: "/") { 176 | requirePath += "\(SECompiler.relativePath!)" 177 | } 178 | requirePath += "\(file)" 179 | 180 | // Add require to the list 181 | SECompiler.requireList.insert(requirePath) 182 | 183 | do { 184 | // Recurse 185 | let fileContents = try SECompiler.getFileContents(path: requirePath) 186 | SECompiler.generateRequireList(path: path, content: fileContents) 187 | } 188 | catch { 189 | let errorStr = "\n\(path):\(lineNum+1):\(1): error: could not find file \(file)\n\(line)\n^\n" 190 | let output = SECompiler.getErrors(errorStr) 191 | SEResponse.outputHTML(status: 404, title: "File Not Found", style: SECompiler.lineNumberStyle, body: output, compilationError: true) 192 | } 193 | } 194 | } 195 | } 196 | } 197 | } 198 | 199 | 200 | 201 | // Write out the .sources file to the compileBinaryLocation 202 | private class func createSourcesFile(requiresList: Set) { 203 | 204 | // Get path that is not part of the document root and make dirs for that 205 | let cacheDir = "\(SECompiler.binaryCompilationLocation)\(SECompiler.relativePath!)" 206 | SEShell.bash("mkdir -p \(cacheDir)") 207 | 208 | let fout = "\(SECompiler.executableName!).sources" 209 | let fileUrl = URL(fileURLWithPath: "\(cacheDir)\(fout)") 210 | let textToWriteOut = requiresList.joined(separator: "\n") 211 | 212 | do { 213 | try textToWriteOut.write(to: fileUrl, atomically: false, encoding: .utf8) 214 | } 215 | catch { 216 | SEShell.stdErr.write(error.localizedDescription) 217 | exit(-1) 218 | } 219 | } 220 | 221 | 222 | 223 | // Checks to see if any of the source files are newer than executable 224 | private class func isBinaryCurrent() -> Bool { 225 | 226 | let fileManager = FileManager.default 227 | let sourcesFile = "\(SECompiler.fullExecutablePath).sources" 228 | 229 | // Ensure binary exits 230 | guard fileManager.fileExists(atPath: SECompiler.fullExecutablePath) else { 231 | return false 232 | } 233 | 234 | // Ensure .sources file exists 235 | guard fileManager.fileExists(atPath: sourcesFile) else { 236 | return false 237 | } 238 | 239 | 240 | do { 241 | // Check the required files 242 | let sources = try String(contentsOfFile: sourcesFile, encoding: .utf8).components(separatedBy: .newlines) 243 | 244 | // Get date of .sources file 245 | let sourcesFileAttrs = try fileManager.attributesOfItem(atPath: SECompiler.fullExecutablePath) 246 | if let sourcesFileCreationDate = sourcesFileAttrs[.modificationDate] as? Date { 247 | 248 | // Iterated through required files, check to see if that file's creation date is older 249 | for file in sources { 250 | let absolutePath = file 251 | let fileAttrs = try fileManager.attributesOfItem(atPath: absolutePath) 252 | 253 | if let fileCreationDate = fileAttrs[.modificationDate] as? Date { 254 | // File is newer than .sources file 255 | if sourcesFileCreationDate < fileCreationDate { 256 | return false 257 | } 258 | } 259 | } 260 | } 261 | 262 | // Check the executable against SECore and main.swift 263 | let executableAttrs = try fileManager.attributesOfItem(atPath: "\(SECompiler.binaryCompilationLocation)/\(SECompiler.entryPointFilename)") 264 | if let executableCreationDate = executableAttrs[.modificationDate] as? Date { 265 | 266 | // SECoreLib 267 | let seCoreLibAttrs = try fileManager.attributesOfItem(atPath: SECompiler.seCoreObjectList!) 268 | if let seCoreCreationDate = seCoreLibAttrs[.modificationDate] as? Date { 269 | 270 | // If the SECorelib is more recent than the executable, not current 271 | if seCoreCreationDate > executableCreationDate { 272 | return false 273 | } 274 | } 275 | 276 | // main.swift 277 | let mainAttrs = try fileManager.attributesOfItem(atPath: SECompiler.seMain) 278 | if let mainCreationDate = mainAttrs[.modificationDate] as? Date { 279 | 280 | // Main is more recent than executbale, not current 281 | if mainCreationDate > executableCreationDate { 282 | return false 283 | } 284 | } 285 | } 286 | } 287 | catch { 288 | SEShell.stdErr.write(error.localizedDescription) 289 | exit(-1) 290 | } 291 | 292 | 293 | // All required files are newer than .sources file or executable 294 | return true 295 | } 296 | 297 | 298 | 299 | private class func generateObjectFile(sourceUrl: String, isMain: Bool = false) { 300 | 301 | } 302 | 303 | 304 | 305 | private class func listOfObjectFiles(baseDir: String) throws -> [String] { 306 | let text = try SECompiler.getFileContents(path: baseDir + "/objectslist.txt") 307 | // split each line into array item, filter out any empty lines, and append absolute URLs 308 | let list = text.components(separatedBy: "\n").filter{$0 != ""}.map{baseDir + "/" + $0} 309 | return list 310 | } 311 | 312 | 313 | 314 | private class func listOfModulemapFiles(baseDir:String) throws -> [String] { 315 | let text = try SECompiler.getFileContents(path: baseDir + "/modulemaplist.txt") 316 | // split each line into array item, filter out any empty lines, and append absolute URLs 317 | let list = text.components(separatedBy: "\n").filter{$0 != ""}.map{baseDir + "/" + $0} 318 | return list 319 | } 320 | 321 | 322 | 323 | private class func getFileContents(path: String) throws -> String { 324 | let text = try String(contentsOfFile: path as String, encoding: .utf8) 325 | return text 326 | } 327 | 328 | 329 | 330 | private class func debugOut(_ resp : Any) { 331 | print("Content-type: text/html") 332 | print("") 333 | print("
")
334 | 		print("\(resp)")
335 | 		print("
") 336 | } 337 | 338 | 339 | } 340 | 341 | 342 | /* Private helper functions for starting the request/compilation process */ 343 | extension SECompiler { 344 | 345 | private class func _excuteRequest(path: String) { 346 | 347 | //SECompiler.compileFile(fileUri: path) 348 | 349 | // Check if binary if current; if not, compile file 350 | if !SECompiler.isBinaryCurrent() { 351 | SECompiler.compileFile(fileUri: path) 352 | } 353 | 354 | 355 | // Execute the binary 356 | #if false 357 | let (stdOut, stdErr, status) = SEShell.run([fullExecutablePath]) 358 | if (status != 0) { 359 | print(stdErr) 360 | // let output = SECompiler.getErrors(stdErr) 361 | // SEResponse.outputHTML(status: 500, title: nil, style: SECompiler.lineNumberStyle, body: output, compilationError: true) 362 | } 363 | print(status) 364 | print(stdErr) 365 | print(stdOut) 366 | #else 367 | SEShell.runBinary(fullExecutablePath) 368 | 369 | #endif 370 | exit(0) 371 | } 372 | 373 | 374 | private class func setPathComponents(forPath path: String) { 375 | // Get executable name and relative path 376 | if let filename = path.components(separatedBy: "/").last, let execName = filename.components(separatedBy: ".").first { 377 | SECompiler.executableName = execName 378 | SECompiler.relativePath = String(path.dropFirst(SEGlobals.DOCUMENT_ROOT.count).dropLast("/\(filename)".count)) 379 | } 380 | else { 381 | // Could not get path componenets, can't proceed 382 | exit(-1) 383 | } 384 | } 385 | 386 | } 387 | 388 | 389 | 390 | 391 | /* Private helper methods for displaying errors and outputting code */ 392 | extension SECompiler { 393 | 394 | private class func getErrors(_ error: String) -> String { 395 | let arrError = SECompiler.matchError(error) 396 | return SECompiler.decoreError(sError: error, errors: arrError) 397 | } 398 | 399 | private class func matchError(_ error: String) -> [NSTextCheckingResult] { 400 | let errorPattern = "\n(?[^:\n]+)" + // get the filename 401 | ":" + 402 | // get the line number 403 | "(?[^:\n]+)" + // match everything except a colon and new line 404 | ":" + // match the colon 405 | // get char position 406 | "(?[^:\n]+)" + 407 | ":" + 408 | // get the type of annotation (error|warning|etc) 409 | "(?[^:\n]+)" + 410 | ":" + 411 | // get the description of error 412 | "(?[^\n]+)" + 413 | "(?=\n)" + // detect end of line (but don't consume it) 414 | // iterate through the compile details 415 | "(?" + 416 | "(?:" + // dont capture this block as we're capturing this in the parent 417 | "\n" + 418 | "(?!(/))" + // do a negative lookahead to make sure that the next line doesn't start with a forward slash ("/"), as this would most likely indicate that this is the file column of another error line 419 | "[^\n]+" + // look for anything that doesn't start with the filename 420 | "(?=\n)" + 421 | ")*" + 422 | ")" 423 | //let nsString = NSString(string: error) 424 | // arrError = matches(for: errorPattern, in: error).map { nsString.substring(with: $0.range)} 425 | // return arrError 426 | return matches(for: errorPattern, in: error) 427 | } 428 | 429 | private class func matches(for regex: String, in text: String) -> [NSTextCheckingResult] { 430 | do { 431 | let regex = try NSRegularExpression(pattern: regex) 432 | let nsString = NSString(string: text) 433 | return regex.matches(in: text, range: NSRange(location: 0, length: nsString.length)) 434 | // return results.map { nsString.substring(with: $0.range)} 435 | } 436 | catch { 437 | return [] 438 | } 439 | } 440 | 441 | private class func decoreError(sError: String, errors: [NSTextCheckingResult]) -> String { 442 | let numErrors = errors.count 443 | 444 | var output = "

\(numErrors) issue\(numErrors > 1 ? "s" : "") found on this page

" 445 | 446 | for i in 0.." 452 | output += SECompiler.getCodeSnippetFromFile(fileName: SECompiler.getTagValue(sError, textCheckingResult: error, key: 1), lineNo: Int(SECompiler.getTagValue(sError, textCheckingResult: error, key: 2))!) 453 | output += "Compiler diagnostics: " 454 | output += "\(SECompiler.getTagValue(sError, textCheckingResult: error, key: 5)) " 455 | output += "" 456 | output += "
"
457 |             output += "\(SECompiler.getTagValue(sError, textCheckingResult: error, key: 6))"
458 |             output += "
" 459 | } 460 | return output 461 | } 462 | 463 | private class func getTagValue(_ sError: String, textCheckingResult: NSTextCheckingResult, key: Int) -> String { 464 | let range = textCheckingResult.range(at: key) 465 | let stringRange = Range(range, in: sError)! 466 | return String(sError[stringRange]) 467 | } 468 | 469 | private class func getCodeSnippetFromFile(fileName: String, lineNo: Int, span: Int = 5) -> String { 470 | do { 471 | let data = try String(contentsOfFile: fileName, encoding: .utf8) 472 | let lines = data.components(separatedBy: .newlines) 473 | let startLine = (lineNo - span) < 1 ? 1 : (lineNo - span) 474 | let endLine = (startLine + span*2) > lines.count ? lines.count : (startLine + span*2) 475 | 476 | var output = "
 0 ? startLine : 1)\" " +
482 |             ">"
483 |             for i in startLine...endLine {
484 |                 if lines[i-1] != "" {
485 |                     output += "
" 486 | } 487 | if i == lineNo { 488 | output += "\(lines[i-1])" 489 | } else { 490 | output += ("\(lines[i-1])" + "") 491 | } 492 | } 493 | output += "
" 494 | return output 495 | } 496 | catch { 497 | 498 | } 499 | return "" 500 | } 501 | 502 | private class var lineNumberStyle: String { 503 | return "pre.line-numbers {" + 504 | "position: relative;" + 505 | "padding-left: 3.8em;" + 506 | "counter-reset: linenumber;" + 507 | "}" + 508 | "pre.line-numbers > code {" + 509 | "position: relative;" + 510 | "}" + 511 | ".line-numbers .line-numbers-rows {" + 512 | "position: absolute;" + 513 | "pointer-events: none;" + 514 | "top: 0;" + 515 | "font-size: 100%;" + 516 | "left: -3.8em;" + 517 | "width: 3em; " + /* works for line-numbers below 1000 lines */ 518 | "letter-spacing: -1px;" + 519 | "border-right: 1px solid #999;" + 520 | "-webkit-user-select: none;" + 521 | "-moz-user-select: none;" + 522 | "-ms-user-select: none;" + 523 | "user-select: none;" + 524 | "}" + 525 | ".line-numbers-rows > span {" + 526 | "pointer-events: none;" + 527 | "display: block;" + 528 | "counter-increment: linenumber;" + 529 | "}" + 530 | ".line-numbers-rows > span:before {" + 531 | "content: counter(linenumber);" + 532 | "color: #999;" + 533 | "display: block;" + 534 | "padding-right: 0.8em;" + 535 | "text-align: right;" + 536 | "}" + 537 | ".line-highlight {" + 538 | "background: hsla(11, 96%, 50%,.28);" + 539 | "background: linear-gradient(to right, hsla(11, 96%, 50%,.21) 70%, hsla(11, 96%, 50%,0));" + 540 | "}" 541 | } 542 | 543 | } 544 | 545 | 546 | -------------------------------------------------------------------------------- /Sources/SEProcessorLib/SEGlobals.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public class SEGlobals { 4 | public static let REQUIRE_KEY = "//se: require " 5 | public static var DOCUMENT_ROOT = "" 6 | public static var SECORE_LOCATION = "/etc/swiftengine/magic/SECore" 7 | } 8 | 9 | 10 | -------------------------------------------------------------------------------- /Sources/SEProcessorLib/SEResponse.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public class SEResponse { 4 | let stdin = FileHandle.standardInput 5 | let stdout = FileHandle.standardOutput 6 | let stderror = FileHandle.standardError 7 | 8 | public init() { 9 | 10 | } 11 | 12 | func stdout(_ string: String) { 13 | guard let data = string.data(using: .utf8) else { return } 14 | stdout.write(data) 15 | } 16 | 17 | func stdout(_ data: Data) { 18 | stdout.write(data) 19 | } 20 | 21 | public func fileNotFound(_ name: String) { 22 | SEResponse.outputHTML(status: 404, title: "File Not Found", style: nil, body: "

404- File Not Found


Could not find file: \(name)") 23 | } 24 | 25 | func fileNotSupported(_ extensionFile: String) { 26 | SEResponse.outputHTML(status: 500, title: "File Not Supported", style: nil, body: "

500- File Type Not Supported


File type not supported: \(extensionFile)") 27 | } 28 | 29 | class func outputHTML(status: Int, title: String?, style: String?, body: String, compilationError: Bool = false) { 30 | print("HTTP/1.1 \(status)") 31 | print("Content-type: text/html") 32 | print("") 33 | print("") 34 | print("") 35 | print("") 36 | if let title = title { 37 | print("\(title)") 38 | } 39 | if let style = style { 40 | print("") 41 | } 42 | print("") 43 | print("") 44 | print(body) 45 | if compilationError { 46 | print("


") 47 | } 48 | print("") 49 | print("") 50 | exit(0) 51 | } 52 | 53 | 54 | public func processStaticFile(path: String) { 55 | let fileExtension = path.components(separatedBy: ".")[1] 56 | if let contentType = ContentType.shared.getContentType(forExtension: fileExtension) { 57 | let url = URL(fileURLWithPath: path) 58 | let data = try! Data(contentsOf: url) 59 | stdout( "Content-type: \(contentType)\n") 60 | stdout( "\n") 61 | stdout(data) 62 | exit(0) 63 | } 64 | else { 65 | self.fileNotSupported(fileExtension) 66 | } 67 | } 68 | 69 | func getRawPostData() -> String? { 70 | //https://developer.apple.com/documentation/foundation/filehandle 71 | let data = stdin.readDataToEndOfFile() 72 | let dataStr = String(data: data, encoding: String.Encoding.utf8) 73 | return dataStr 74 | } 75 | 76 | func getBase64Data(path: String) -> String? { 77 | let data = NSData(contentsOfFile: path) 78 | let dataStr = data!.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0)) 79 | return dataStr 80 | } 81 | 82 | func getEnvironmentVar(_ name: String) -> String? { 83 | return ProcessInfo.processInfo.environment[name] 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Sources/SEProcessorLib/SERoute.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | 4 | public enum ExcutableType { 5 | case notFound 6 | case swift 7 | case staticFile 8 | } 9 | 10 | public class SERoute { 11 | public init(directory: String) { 12 | SEGlobals.DOCUMENT_ROOT = directory 13 | } 14 | 15 | public init() { 16 | 17 | } 18 | 19 | public func doesRouterExist() -> Bool { 20 | let routerPath = "\(SEGlobals.DOCUMENT_ROOT)/Router.swift" 21 | return SECommon.checkExitsFile(filePath: routerPath) 22 | } 23 | 24 | public func getExecutableType(_ requestFile: String) -> (ExcutableType, String) { 25 | let excutePath = SEGlobals.DOCUMENT_ROOT + requestFile 26 | if requestFile.range(of: ".") != nil { 27 | let fileExtension = requestFile.components(separatedBy: ".")[1] 28 | 29 | if fileExtension == "swift" { 30 | return (.swift, excutePath) 31 | } 32 | else { 33 | return (.staticFile, excutePath) 34 | } 35 | } 36 | else { 37 | // Add extension .swift 38 | let paths = requestFile.components(separatedBy: "/") 39 | var tempPath = SEGlobals.DOCUMENT_ROOT 40 | for i in 1.. (stdOut : String, stdErr : String, status : Int32) { 15 | return run(args) 16 | } 17 | 18 | 19 | @discardableResult 20 | public class func run(_ args: [String] ) -> (stdOut : String, stdErr : String, status : Int32) { 21 | 22 | //let fm = FileManager.default 23 | //let pwd = fm.currentDirectoryPath 24 | 25 | #if swift(>=3.1) 26 | let task = Process() 27 | #else 28 | let task = Task() 29 | #endif 30 | 31 | let pipeStdOut = Pipe() 32 | let pipeStdErr = Pipe() 33 | task.standardOutput = pipeStdOut 34 | task.standardError = pipeStdErr 35 | 36 | //var envShell = ProcessInfo.processInfo.environment 37 | let env = [ 38 | // "TMPDIR": "/var/folders/44/xm15b4ks6dv8xn07w62cf2nw0000gn/T/", 39 | // "SDKROOT":"", 40 | "PATH":"/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin", 41 | // "PWD":"/tmp", 42 | 43 | ] 44 | task.environment = env 45 | var vArgs = args 46 | task.launchPath = vArgs[0] //"/usr/bin/env" 47 | vArgs.remove(at:0) 48 | task.arguments = vArgs 49 | task.launch() 50 | task.waitUntilExit() 51 | 52 | let dataStdOut = pipeStdOut.fileHandleForReading.readDataToEndOfFile() 53 | let stdOut = String(data: dataStdOut, encoding: String.Encoding.utf8) ?? "" 54 | //let stdOut = output.replacingOccurrences(of: "\n", with: "", options: .literal, range: nil) 55 | 56 | let dataStdErr = pipeStdErr.fileHandleForReading.readDataToEndOfFile() 57 | let stdErr = String(data: dataStdErr, encoding: String.Encoding.utf8) ?? "" 58 | 59 | let status = task.terminationStatus 60 | 61 | return (stdOut, stdErr, status) 62 | } 63 | 64 | @discardableResult 65 | public class func bash(_ cmd: String) -> 66 | (stdOut : String, stdErr : String, status : Int32) { 67 | 68 | //let fm = FileManager.default 69 | //let pwd = fm.currentDirectoryPath 70 | 71 | #if swift(>=3.1) 72 | let task = Process() 73 | #else 74 | let task = Task() 75 | #endif 76 | // uncomment lines below to pass through the env 77 | //var envShell = ProcessInfo.processInfo.environment 78 | //task.environment = envShell 79 | 80 | // create a pipe for capturing the output from shell 81 | let pipeStdOut = Pipe() 82 | let pipeStdErr = Pipe() 83 | task.standardOutput = pipeStdOut 84 | task.standardError = pipeStdErr 85 | 86 | task.launchPath = "/usr/bin/env" 87 | task.arguments = ["/bin/bash","-c", cmd]//args 88 | task.launch() 89 | task.waitUntilExit() 90 | 91 | let dataStdOut = pipeStdOut.fileHandleForReading.readDataToEndOfFile() 92 | let stdOut = String(data: dataStdOut, encoding: String.Encoding.utf8) ?? "" 93 | //let stdOut = output.replacingOccurrences(of: "\n", with: "", options: .literal, range: nil) 94 | 95 | let dataStdErr = pipeStdErr.fileHandleForReading.readDataToEndOfFile() 96 | let stdErr = String(data: dataStdErr, encoding: String.Encoding.utf8) ?? "" 97 | 98 | let status = task.terminationStatus 99 | 100 | return (stdOut, stdErr, status) 101 | 102 | //return task.terminationStatus 103 | } 104 | 105 | public class func runBinary(_ fileUri : String){ 106 | // first close all the current piple 107 | // let stdin = FileHandle.standardInput 108 | // let stdout = FileHandle.standardOutput 109 | // let stderror = FileHandle.standardError 110 | 111 | //int fds[2]; 112 | 113 | //let fds = [UnsafeMutablePointer!](repeating: nil, count: 64) 114 | // var fds: [Int32] = [-1, -1] 115 | // 116 | // var pipe_in: [Int32] = [-1, -1] 117 | // var pipe_out: [Int32] = [-1, -1] 118 | // var pipe_err: [Int32] = [-1, -1] 119 | // 120 | // pipe(&pipe_in) 121 | // pipe(&pipe_out) 122 | // pipe(&pipe_err) 123 | 124 | 125 | //close(pipe_in[1]); 126 | //close(pipe_out[0]); 127 | //close(pipe_err[0]); 128 | 129 | // dup2(pipe_in[0], 0); 130 | // dup2(pipe_out[1], 1); 131 | // dup2(pipe_err[1], 2); 132 | 133 | // close(pipe_in[0]); 134 | // close(pipe_out[1]); 135 | // close(pipe_err[1]); 136 | 137 | //pipe(&fds) 138 | //close(STDIN_FILENO) 139 | //dup2(fds[0], STDIN_FILENO) 140 | 141 | //close(fds[1]); 142 | //dup2(fds[0], STDIN_FILENO); 143 | 144 | //stdin.closeFile() 145 | //close(STDIN_FILENO) 146 | //stdout.closeFile() 147 | //stderror.closeFile() 148 | 149 | let args = [fileUri] 150 | 151 | // Array of UnsafeMutablePointer 152 | let cargs = args.map { strdup($0) } + [nil] 153 | 154 | 155 | //exec(fileUri) 156 | execv(fileUri, cargs) 157 | //execvp(fileUri, cargs) 158 | //execl(fileUri, "main") 159 | } 160 | 161 | public class func runBinary() { 162 | 163 | let args = ["ls", "-l", "/Library"] 164 | 165 | // Array of UnsafeMutablePointer 166 | let cargs = args.map { strdup($0) } + [nil] 167 | 168 | execv("/bin/ls", cargs) 169 | } 170 | 171 | 172 | } 173 | -------------------------------------------------------------------------------- /Sources/SwiftEngineServer/ByteBuffer+ForString.swift: -------------------------------------------------------------------------------- 1 | import NIO 2 | 3 | 4 | public extension ByteBuffer { 5 | static func forString(_ string: String) -> ByteBuffer { 6 | var buf = ByteBufferAllocator().buffer(capacity: string.utf8.count) 7 | buf.write(string: string) 8 | return buf 9 | } 10 | } 11 | 12 | -------------------------------------------------------------------------------- /Sources/SwiftEngineServer/HTTPHandler.swift: -------------------------------------------------------------------------------- 1 | // HTTPHandler.swift 2 | import NIO 3 | import NIOHTTP1 4 | 5 | extension String { 6 | func chopPrefix(_ prefix: String) -> String? { 7 | if self.unicodeScalars.starts(with: prefix.unicodeScalars) { 8 | return String(self[self.index(self.startIndex, offsetBy: prefix.count)...]) 9 | } else { 10 | return nil 11 | } 12 | } 13 | 14 | func containsDotDot() -> Bool { 15 | for idx in self.indices { 16 | if self[idx] == "." && idx < self.index(before: self.endIndex) && self[self.index(after: idx)] == "." { 17 | return true 18 | } 19 | } 20 | return false 21 | } 22 | } 23 | 24 | public func httpResponseHead(request: HTTPRequestHead, status: HTTPResponseStatus, headers: HTTPHeaders = HTTPHeaders()) -> HTTPResponseHead { 25 | var head = HTTPResponseHead(version: request.version, status: status, headers: headers) 26 | let connectionHeaders: [String] = head.headers[canonicalForm: "connection"].map { $0.lowercased() } 27 | 28 | if !connectionHeaders.contains("keep-alive") && !connectionHeaders.contains("close") { 29 | // the user hasn't pre-set either 'keep-alive' or 'close', so we might need to add headers 30 | switch (request.isKeepAlive, request.version.major, request.version.minor) { 31 | case (true, 1, 0): 32 | // HTTP/1.0 and the request has 'Connection: keep-alive', we should mirror that 33 | head.headers.add(name: "Connection", value: "keep-alive") 34 | case (false, 1, let n) where n >= 1: 35 | // HTTP/1.1 (or treated as such) and the request has 'Connection: close', we should mirror that 36 | head.headers.add(name: "Connection", value: "close") 37 | default: 38 | // we should match the default or are dealing with some HTTP that we don't support, let's leave as is 39 | () 40 | } 41 | } 42 | return head 43 | } 44 | 45 | 46 | 47 | public final class HTTPHandler1: ChannelInboundHandler { 48 | private enum FileIOMethod { 49 | case sendfile 50 | case nonblockingFileIO 51 | } 52 | public typealias InboundIn = HTTPServerRequestPart 53 | public typealias OutboundOut = HTTPServerResponsePart 54 | 55 | private enum State { 56 | case idle 57 | case waitingForRequestBody 58 | case sendingResponse 59 | 60 | mutating func requestReceived() { 61 | precondition(self == .idle, "Invalid state for request received: \(self)") 62 | self = .waitingForRequestBody 63 | } 64 | 65 | mutating func requestComplete() { 66 | precondition(self == .waitingForRequestBody, "Invalid state for request complete: \(self)") 67 | self = .sendingResponse 68 | } 69 | 70 | mutating func responseComplete() { 71 | precondition(self == .sendingResponse, "Invalid state for response complete: \(self)") 72 | self = .idle 73 | } 74 | } 75 | 76 | private var buffer: ByteBuffer! = nil 77 | private var keepAlive = false 78 | private var state = State.idle 79 | private let htdocsPath: String 80 | 81 | private var infoSavedRequestHead: HTTPRequestHead? 82 | private var infoSavedBodyBytes: Int = 0 83 | 84 | private var continuousCount: Int = 0 85 | 86 | private var handler: ((ChannelHandlerContext, HTTPServerRequestPart) -> Void)? 87 | private var handlerFuture: EventLoopFuture? 88 | private let fileIO: NonBlockingFileIO 89 | 90 | public init(fileIO: NonBlockingFileIO, htdocsPath: String) { 91 | self.htdocsPath = htdocsPath 92 | self.fileIO = fileIO 93 | } 94 | 95 | func handleInfo(ctx: ChannelHandlerContext, request: HTTPServerRequestPart) { 96 | print("handleInfo()\n") 97 | switch request { 98 | case .head(let request): 99 | self.infoSavedRequestHead = request 100 | self.infoSavedBodyBytes = 0 101 | self.keepAlive = request.isKeepAlive 102 | self.state.requestReceived() 103 | case .body(buffer: let buf): 104 | self.infoSavedBodyBytes += buf.readableBytes 105 | case .end: 106 | self.state.requestComplete() 107 | let response = """ 108 | HTTP method: \(self.infoSavedRequestHead!.method)\r 109 | URL: \(self.infoSavedRequestHead!.uri)\r 110 | body length: \(self.infoSavedBodyBytes)\r 111 | headers: \(self.infoSavedRequestHead!.headers)\r 112 | client: \(ctx.remoteAddress?.description ?? "zombie")\r 113 | IO: SwiftNIO Electric Boogaloo™️\r\n 114 | """ 115 | self.buffer.clear() 116 | self.buffer.write(string: response) 117 | var headers = HTTPHeaders() 118 | headers.add(name: "Content-Length", value: "\(response.utf8.count)") 119 | ctx.write(self.wrapOutboundOut(.head(httpResponseHead(request: self.infoSavedRequestHead!, status: .ok, headers: headers))), promise: nil) 120 | ctx.write(self.wrapOutboundOut(.body(.byteBuffer(self.buffer))), promise: nil) 121 | self.completeResponse(ctx, trailers: nil, promise: nil) 122 | } 123 | } 124 | 125 | func handleEcho(ctx: ChannelHandlerContext, request: HTTPServerRequestPart) { 126 | print("handleEcho()\n") 127 | self.handleEcho(ctx: ctx, request: request, balloonInMemory: false) 128 | } 129 | 130 | func handleEcho(ctx: ChannelHandlerContext, request: HTTPServerRequestPart, balloonInMemory: Bool = false) { 131 | print("handleEcho\n") 132 | switch request { 133 | case .head(let request): 134 | self.keepAlive = request.isKeepAlive 135 | self.infoSavedRequestHead = request 136 | self.state.requestReceived() 137 | if balloonInMemory { 138 | self.buffer.clear() 139 | } else { 140 | ctx.writeAndFlush(self.wrapOutboundOut(.head(httpResponseHead(request: request, status: .ok))), promise: nil) 141 | } 142 | case .body(buffer: var buf): 143 | if balloonInMemory { 144 | self.buffer.write(buffer: &buf) 145 | } else { 146 | ctx.writeAndFlush(self.wrapOutboundOut(.body(.byteBuffer(buf))), promise: nil) 147 | } 148 | case .end: 149 | self.state.requestComplete() 150 | if balloonInMemory { 151 | var headers = HTTPHeaders() 152 | headers.add(name: "Content-Length", value: "\(self.buffer.readableBytes)") 153 | ctx.write(self.wrapOutboundOut(.head(httpResponseHead(request: self.infoSavedRequestHead!, status: .ok, headers: headers))), promise: nil) 154 | ctx.write(self.wrapOutboundOut(.body(.byteBuffer(self.buffer))), promise: nil) 155 | self.completeResponse(ctx, trailers: nil, promise: nil) 156 | } else { 157 | self.completeResponse(ctx, trailers: nil, promise: nil) 158 | } 159 | } 160 | } 161 | 162 | func handleJustWrite(ctx: ChannelHandlerContext, request: HTTPServerRequestPart, statusCode: HTTPResponseStatus = .ok, string: String, trailer: (String, String)? = nil, delay: TimeAmount = .nanoseconds(0)) { 163 | 164 | print("handleJustWrite\n") 165 | switch request { 166 | case .head(let request): 167 | self.keepAlive = request.isKeepAlive 168 | self.state.requestReceived() 169 | ctx.writeAndFlush(self.wrapOutboundOut(.head(httpResponseHead(request: request, status: .ok))), promise: nil) 170 | case .body(buffer: _): 171 | () 172 | case .end: 173 | self.state.requestComplete() 174 | _ = ctx.eventLoop.scheduleTask(in: delay) { () -> Void in 175 | var buf = ctx.channel.allocator.buffer(capacity: string.utf8.count) 176 | buf.write(string: string) 177 | ctx.writeAndFlush(self.wrapOutboundOut(.body(.byteBuffer(buf))), promise: nil) 178 | var trailers: HTTPHeaders? = nil 179 | if let trailer = trailer { 180 | trailers = HTTPHeaders() 181 | trailers?.add(name: trailer.0, value: trailer.1) 182 | } 183 | 184 | self.completeResponse(ctx, trailers: trailers, promise: nil) 185 | } 186 | } 187 | } 188 | 189 | func handleContinuousWrites(ctx: ChannelHandlerContext, request: HTTPServerRequestPart) { 190 | print("handleContinuousWrites\n") 191 | switch request { 192 | case .head(let request): 193 | self.keepAlive = request.isKeepAlive 194 | self.continuousCount = 0 195 | self.state.requestReceived() 196 | func doNext() { 197 | self.buffer.clear() 198 | self.continuousCount += 1 199 | self.buffer.write(string: "line \(self.continuousCount)\n") 200 | ctx.writeAndFlush(self.wrapOutboundOut(.body(.byteBuffer(self.buffer)))).map { 201 | _ = ctx.eventLoop.scheduleTask(in: .milliseconds(400), doNext) 202 | }.whenFailure { (_: Error) in 203 | self.completeResponse(ctx, trailers: nil, promise: nil) 204 | } 205 | } 206 | ctx.writeAndFlush(self.wrapOutboundOut(.head(httpResponseHead(request: request, status: .ok))), promise: nil) 207 | doNext() 208 | case .end: 209 | self.state.requestComplete() 210 | default: 211 | break 212 | } 213 | } 214 | 215 | func handleMultipleWrites(ctx: ChannelHandlerContext, request: HTTPServerRequestPart, strings: [String], delay: TimeAmount) { 216 | print("handleMultipleWrites\n") 217 | switch request { 218 | case .head(let request): 219 | self.keepAlive = request.isKeepAlive 220 | self.continuousCount = 0 221 | self.state.requestReceived() 222 | func doNext() { 223 | self.buffer.clear() 224 | self.buffer.write(string: strings[self.continuousCount]) 225 | self.continuousCount += 1 226 | ctx.writeAndFlush(self.wrapOutboundOut(.body(.byteBuffer(self.buffer)))).whenSuccess { 227 | if self.continuousCount < strings.count { 228 | _ = ctx.eventLoop.scheduleTask(in: delay, doNext) 229 | } else { 230 | self.completeResponse(ctx, trailers: nil, promise: nil) 231 | } 232 | } 233 | } 234 | ctx.writeAndFlush(self.wrapOutboundOut(.head(httpResponseHead(request: request, status: .ok))), promise: nil) 235 | doNext() 236 | case .end: 237 | self.state.requestComplete() 238 | default: 239 | break 240 | } 241 | } 242 | 243 | func dynamicHandler(request reqHead: HTTPRequestHead) -> ((ChannelHandlerContext, HTTPServerRequestPart) -> Void)? { 244 | print("dynamicHandler\n") 245 | if let howLong = reqHead.uri.chopPrefix("/dynamic/write-delay/") { 246 | return { ctx, req in 247 | self.handleJustWrite(ctx: ctx, 248 | request: req, string: "Hello World\r\n", 249 | delay: TimeAmount.Value(howLong).map { .milliseconds($0) } ?? .seconds(0)) 250 | } 251 | } 252 | 253 | switch reqHead.uri { 254 | case "/dynamic/echo": 255 | return self.handleEcho 256 | case "/dynamic/echo_balloon": 257 | return { self.handleEcho(ctx: $0, request: $1, balloonInMemory: true) } 258 | case "/dynamic/pid": 259 | return { ctx, req in self.handleJustWrite(ctx: ctx, request: req, string: "\(getpid())") } 260 | case "/dynamic/write-delay": 261 | return { ctx, req in self.handleJustWrite(ctx: ctx, request: req, string: "Hello World\r\n", delay: .milliseconds(100)) } 262 | case "/dynamic/info": 263 | return self.handleInfo 264 | case "/dynamic/trailers": 265 | return { ctx, req in self.handleJustWrite(ctx: ctx, request: req, string: "\(getpid())\r\n", trailer: ("Trailer-Key", "Trailer-Value")) } 266 | case "/dynamic/continuous": 267 | return self.handleContinuousWrites 268 | case "/dynamic/count-to-ten": 269 | return { self.handleMultipleWrites(ctx: $0, request: $1, strings: (1...10).map { "\($0)" }, delay: .milliseconds(100)) } 270 | case "/dynamic/client-ip": 271 | return { ctx, req in self.handleJustWrite(ctx: ctx, request: req, string: "\(ctx.remoteAddress.debugDescription)") } 272 | default: 273 | return { ctx, req in self.handleJustWrite(ctx: ctx, request: req, statusCode: .notFound, string: "not found") } 274 | } 275 | } 276 | 277 | private func handleFile(ctx: ChannelHandlerContext, request: HTTPServerRequestPart, ioMethod: FileIOMethod, path: String) { 278 | print("handleFile\n") 279 | self.buffer.clear() 280 | 281 | func sendErrorResponse(request: HTTPRequestHead, _ error: Error) { 282 | var body = ctx.channel.allocator.buffer(capacity: 128) 283 | let response = { () -> HTTPResponseHead in 284 | switch error { 285 | case let e as IOError where e.errnoCode == ENOENT: 286 | body.write(staticString: "IOError (not found)\r\n") 287 | return httpResponseHead(request: request, status: .notFound) 288 | case let e as IOError: 289 | body.write(staticString: "IOError (other)\r\n") 290 | body.write(string: e.description) 291 | body.write(staticString: "\r\n") 292 | return httpResponseHead(request: request, status: .notFound) 293 | default: 294 | body.write(string: "\(type(of: error)) error\r\n") 295 | return httpResponseHead(request: request, status: .internalServerError) 296 | } 297 | }() 298 | body.write(string: "\(error)") 299 | body.write(staticString: "\r\n") 300 | ctx.write(self.wrapOutboundOut(.head(response)), promise: nil) 301 | ctx.write(self.wrapOutboundOut(.body(.byteBuffer(body))), promise: nil) 302 | ctx.writeAndFlush(self.wrapOutboundOut(.end(nil)), promise: nil) 303 | ctx.channel.close(promise: nil) 304 | } 305 | 306 | func responseHead(request: HTTPRequestHead, fileRegion region: FileRegion) -> HTTPResponseHead { 307 | var response = httpResponseHead(request: request, status: .ok) 308 | response.headers.add(name: "Content-Length", value: "\(region.endIndex)") 309 | response.headers.add(name: "Content-Type", value: "text/plain; charset=utf-8") 310 | return response 311 | } 312 | 313 | switch request { 314 | case .head(let request): 315 | self.keepAlive = request.isKeepAlive 316 | self.state.requestReceived() 317 | guard !request.uri.containsDotDot() else { 318 | let response = httpResponseHead(request: request, status: .forbidden) 319 | ctx.write(self.wrapOutboundOut(.head(response)), promise: nil) 320 | self.completeResponse(ctx, trailers: nil, promise: nil) 321 | return 322 | } 323 | let path = self.htdocsPath + "/" + path 324 | let fileHandleAndRegion = self.fileIO.openFile(path: path, eventLoop: ctx.eventLoop) 325 | fileHandleAndRegion.whenFailure { 326 | sendErrorResponse(request: request, $0) 327 | } 328 | fileHandleAndRegion.whenSuccess { (file, region) in 329 | switch ioMethod { 330 | case .nonblockingFileIO: 331 | var responseStarted = false 332 | let response = responseHead(request: request, fileRegion: region) 333 | return self.fileIO.readChunked(fileRegion: region, 334 | chunkSize: 32 * 1024, 335 | allocator: ctx.channel.allocator, 336 | eventLoop: ctx.eventLoop) { buffer in 337 | if !responseStarted { 338 | responseStarted = true 339 | ctx.write(self.wrapOutboundOut(.head(response)), promise: nil) 340 | } 341 | return ctx.writeAndFlush(self.wrapOutboundOut(.body(.byteBuffer(buffer)))) 342 | }.then { () -> EventLoopFuture in 343 | let p: EventLoopPromise = ctx.eventLoop.newPromise() 344 | self.completeResponse(ctx, trailers: nil, promise: p) 345 | return p.futureResult 346 | }.thenIfError { error in 347 | if !responseStarted { 348 | let response = httpResponseHead(request: request, status: .ok) 349 | ctx.write(self.wrapOutboundOut(.head(response)), promise: nil) 350 | var buffer = ctx.channel.allocator.buffer(capacity: 100) 351 | buffer.write(string: "fail: \(error)") 352 | ctx.write(self.wrapOutboundOut(.body(.byteBuffer(buffer))), promise: nil) 353 | self.state.responseComplete() 354 | return ctx.writeAndFlush(self.wrapOutboundOut(.end(nil))) 355 | } else { 356 | return ctx.close() 357 | } 358 | }.whenComplete { 359 | _ = try? file.close() 360 | } 361 | case .sendfile: 362 | let response = responseHead(request: request, fileRegion: region) 363 | ctx.write(self.wrapOutboundOut(.head(response)), promise: nil) 364 | ctx.writeAndFlush(self.wrapOutboundOut(.body(.fileRegion(region)))).then { 365 | let p: EventLoopPromise = ctx.eventLoop.newPromise() 366 | self.completeResponse(ctx, trailers: nil, promise: p) 367 | return p.futureResult 368 | }.thenIfError { (_: Error) in 369 | ctx.close() 370 | }.whenComplete { 371 | _ = try? file.close() 372 | } 373 | } 374 | } 375 | case .end: 376 | self.state.requestComplete() 377 | default: 378 | fatalError("oh noes: \(request)") 379 | } 380 | } 381 | 382 | private func completeResponse(_ ctx: ChannelHandlerContext, trailers: HTTPHeaders?, promise: EventLoopPromise?) { 383 | print("completeResponse\n") 384 | self.state.responseComplete() 385 | 386 | let promise = self.keepAlive ? promise : (promise ?? ctx.eventLoop.newPromise()) 387 | if !self.keepAlive { 388 | promise!.futureResult.whenComplete { ctx.close(promise: nil) } 389 | } 390 | self.handler = nil 391 | 392 | ctx.writeAndFlush(self.wrapOutboundOut(.end(trailers)), promise: promise) 393 | } 394 | 395 | public func channelRead(ctx: ChannelHandlerContext, data: NIOAny) { 396 | print("channelRead \(data)\n") 397 | let reqPart = self.unwrapInboundIn(data) 398 | if let handler = self.handler { 399 | handler(ctx, reqPart) 400 | print("\thandler exit\n") 401 | return 402 | } 403 | 404 | switch reqPart { 405 | case .head(let request): 406 | print("\thead\n") 407 | if request.uri.unicodeScalars.starts(with: "/dynamic".unicodeScalars) { 408 | self.handler = self.dynamicHandler(request: request) 409 | self.handler!(ctx, reqPart) 410 | return 411 | } else if let path = request.uri.chopPrefix("/sendfile/") { 412 | self.handler = { self.handleFile(ctx: $0, request: $1, ioMethod: .sendfile, path: path) } 413 | self.handler!(ctx, reqPart) 414 | return 415 | } else if let path = request.uri.chopPrefix("/fileio/") { 416 | self.handler = { self.handleFile(ctx: $0, request: $1, ioMethod: .nonblockingFileIO, path: path) } 417 | self.handler!(ctx, reqPart) 418 | return 419 | } 420 | 421 | self.keepAlive = request.isKeepAlive 422 | self.state.requestReceived() 423 | 424 | var responseHead = httpResponseHead(request: request, status: HTTPResponseStatus.ok) 425 | responseHead.headers.add(name: "content-length", value: "12") 426 | let response = HTTPServerResponsePart.head(responseHead) 427 | ctx.write(self.wrapOutboundOut(response), promise: nil) 428 | case .body: 429 | print("\tbody\n") 430 | break 431 | case .end: 432 | print("\tend\n") 433 | self.state.requestComplete() 434 | let content = HTTPServerResponsePart.body(.byteBuffer(buffer!.slice())) 435 | ctx.write(self.wrapOutboundOut(content), promise: nil) 436 | self.completeResponse(ctx, trailers: nil, promise: nil) 437 | } 438 | } 439 | 440 | public func channelReadComplete(ctx: ChannelHandlerContext) { 441 | print("channelReadComplete\n") 442 | ctx.flush() 443 | } 444 | 445 | public func handlerAdded(ctx: ChannelHandlerContext) { 446 | print("handlerAdded\n") 447 | self.buffer = ctx.channel.allocator.buffer(capacity: 12) 448 | self.buffer.write(staticString: "Hello World!") 449 | } 450 | 451 | public func userInboundEventTriggered(ctx: ChannelHandlerContext, event: Any) { 452 | print("userInboundEventTriggered\n") 453 | switch event { 454 | case let evt as ChannelEvent where evt == ChannelEvent.inputClosed: 455 | // The remote peer half-closed the channel. At this time, any 456 | // outstanding response will now get the channel closed, and 457 | // if we are idle or waiting for a request body to finish we 458 | // will close the channel immediately. 459 | switch self.state { 460 | case .idle, .waitingForRequestBody: 461 | ctx.close(promise: nil) 462 | case .sendingResponse: 463 | self.keepAlive = false 464 | } 465 | default: 466 | ctx.fireUserInboundEventTriggered(event) 467 | } 468 | } 469 | } -------------------------------------------------------------------------------- /Sources/SwiftEngineServer/SEHTTPHandler.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import NIO 3 | import NIOHTTP1 4 | import NIOFoundationCompat 5 | #if os(Linux) 6 | import Glibc 7 | #endif 8 | //import Darwin 9 | 10 | 11 | public final class SEHTTPHandler: ChannelInboundHandler { 12 | public typealias InboundIn = HTTPServerRequestPart 13 | //public typealias OutboundOut = HTTPServerResponsePart 14 | public typealias OutboundOut = ByteBuffer 15 | 16 | private let htdocsPath: String 17 | private let fileIO: NonBlockingFileIO 18 | 19 | // Request information 20 | private var infoSavedRequestHead: HTTPRequestHead? 21 | private var keepAlive = false 22 | 23 | // CGI information 24 | private let documentRoot: String 25 | private let pathToSEProcessor: String 26 | 27 | //Request body file handler 28 | private var requestId : String! 29 | private var requestBodyFilePath : String! 30 | private var requestBodyFileHandle : Foundation.FileHandle!//NIO.FileHandle? 31 | 32 | public init(fileIO: NonBlockingFileIO, htdocsPath: String, 33 | documentRoot: String = "/var/swiftengine/www", 34 | pathToSEProcessor: String = "/usr/bin/SEProcessor") { 35 | self.fileIO = fileIO 36 | self.htdocsPath = htdocsPath 37 | self.documentRoot = documentRoot 38 | self.pathToSEProcessor = pathToSEProcessor 39 | 40 | //print("SEHTTPHandler init") 41 | } 42 | 43 | public func handlerAdded(ctx: ChannelHandlerContext) { 44 | } 45 | 46 | public func channelRead(ctx: ChannelHandlerContext, data: NIOAny) { 47 | 48 | 49 | //print("SEHTTPHandler channelRead") 50 | // let outboudDataOut = data //self.wrapOutboundOut(data) 51 | // ctx.fireChannelRead(outboudDataOut) 52 | 53 | if self.requestId == nil { 54 | self.requestId = randomAlphaNumericString(length: 32) 55 | } 56 | 57 | let reqPart = self.unwrapInboundIn(data) 58 | switch reqPart { 59 | case .head(let request): 60 | self.keepAlive = request.isKeepAlive 61 | self.infoSavedRequestHead = request 62 | break 63 | case .body(buffer: var buff): 64 | 65 | // create the file handle if it doesnt already exists 66 | if self.requestBodyFileHandle == nil { 67 | self.requestBodyFilePath = "/tmp/" + self.requestId 68 | let success = FileManager.default.createFile(atPath: self.requestBodyFilePath, contents: nil, attributes: nil) 69 | if (!success) { 70 | SELogger.logUnexpectedCrash("Could not create tmp request body file at: \(self.requestBodyFilePath). Please check permission settings.") 71 | } 72 | self.requestBodyFileHandle = FileHandle(forWritingAtPath: self.requestBodyFilePath) 73 | } 74 | 75 | // write to the file handle 76 | if let _data = buff.readData(length: buff.readableBytes) { 77 | requestBodyFileHandle.write(_data) 78 | // let niofile = NIO.FileHandle(descriptor: _file.fileDescriptor) 79 | // self.fileIO.write(fileHandle: niofile, buffer: buf, eventLoop: ctx.eventLoop) 80 | } 81 | break 82 | case .end: 83 | 84 | // close the request body filehandle if we have one open 85 | if self.requestBodyFileHandle != nil { 86 | self.requestBodyFileHandle.closeFile() 87 | self.requestBodyFileHandle = nil 88 | } 89 | // Ensure we got the request head 90 | guard let request = self.infoSavedRequestHead else { 91 | self.errorInChannelRead(ctx, errorMessage: "Could not process request: No request head") 92 | return 93 | } 94 | 95 | // Script name and query string 96 | let uriComponents = request.uri.components(separatedBy: "?") 97 | var scriptName = uriComponents[0] 98 | if let lastChar = scriptName.last, lastChar == "/" { 99 | scriptName = String(scriptName.dropLast()) 100 | } 101 | 102 | var queryStr = "" 103 | if uriComponents.count > 1 { 104 | queryStr = uriComponents[1] 105 | } 106 | 107 | let fileManager = FileManager.default 108 | 109 | // Set CGI environment variables 110 | var envVars = [ "SCRIPT_NAME" : scriptName, 111 | "QUERY_STRING" : queryStr, 112 | "REQUEST_URI" : request.uri, 113 | "DOCUMENT_ROOT" : self.documentRoot, 114 | "REQUEST_METHOD" : "\(request.method)", 115 | "GATEWAY_INTERFACE" : "CGI/1.1", 116 | "SCRIPT_FILENAME" : "\(self.documentRoot)" + "\(scriptName)", 117 | "SERVER_PROTOCOL" : "HTTP/1.1", 118 | "SERVER_NAME" : "localhost", 119 | 120 | ] 121 | 122 | if let requestId = requestId { 123 | envVars["REQUEST_ID"] = requestId 124 | } 125 | 126 | 127 | // Change all headers to env vars 128 | let headers = request.headers 129 | for header in headers { 130 | let name = "HTTP_" + header.name.uppercased().replacingOccurrences(of: "-", with: "_") 131 | envVars[name] = header.value 132 | } 133 | 134 | // Add the server ip and port 135 | guard let serverAddr = ctx.localAddress, let serverIp = serverAddr.ip, let serverPort = serverAddr.port else { 136 | self.errorInChannelRead(ctx, errorMessage: "Could not process request: No server IP and/or port") 137 | return 138 | } 139 | envVars["SERVER_ADDR"] = serverIp 140 | envVars["SERVER_PORT"] = "\(serverPort)" 141 | 142 | 143 | // Add the remote IP and port 144 | guard let remoteAddr = ctx.remoteAddress, let remoteIp = remoteAddr.ip, let remotePort = remoteAddr.port else { 145 | self.errorInChannelRead(ctx, errorMessage: "Could not process request: No remote IP and/or port") 146 | return 147 | } 148 | envVars["REMOTE_ADDR"] = remoteIp 149 | envVars["REMOTE_PORT"] = "\(remotePort)" 150 | 151 | 152 | //self.printEnvVars(envVars) 153 | var args = [String]() 154 | if let seProcessorLocation = ProcessInfo.processInfo.environment["SEPROCESSOR_LOCATION"] { 155 | args.append(seProcessorLocation) 156 | if !fileManager.fileExists(atPath: seProcessorLocation){ 157 | print("SEProcessor file does not exist: \(seProcessorLocation)") 158 | } 159 | } 160 | else { 161 | args.append(self.pathToSEProcessor) 162 | } 163 | 164 | 165 | if let seCoreLocation = ProcessInfo.processInfo.environment["SECORE_LOCATION"] { 166 | args.append("-secore-location=\(seCoreLocation)") 167 | } 168 | 169 | // Run it 170 | var (stdOut, stdErr, status) = SEShell.run(args, envVars: envVars) 171 | 172 | let output = (status == 0 ? stdOut : stdErr) 173 | 174 | // Log request 175 | SELogger.log(request: request, ip: remoteIp, stdOut: output) 176 | 177 | 178 | // Write it out 179 | var buf = ctx.channel.allocator.buffer(capacity: stdOut.utf8.count) 180 | buf.set(string: output, at: 0) 181 | let nio = NIOAny(ByteBuffer.forString(output)) 182 | ctx.write(nio, promise: nil) 183 | ctx.flush() 184 | ctx.close(promise: nil) 185 | //self.completeResponse(ctx, trailers: nil, promise: nil) 186 | 187 | if self.requestBodyFilePath != nil { 188 | try? fileManager.removeItem(atPath: self.requestBodyFilePath) 189 | } 190 | break 191 | } 192 | } 193 | 194 | 195 | // Generic display error function 196 | private func errorInChannelRead(_ ctx: ChannelHandlerContext, errorMessage errMsg: String = "Could not process request") { 197 | var errBuf = ctx.channel.allocator.buffer(capacity: errMsg.utf8.count) 198 | errBuf.set(string: errMsg, at: 0) 199 | let errNio = NIOAny(ByteBuffer.forString(errMsg)) 200 | ctx.write(errNio, promise: nil) 201 | ctx.flush() 202 | ctx.close(promise: nil) 203 | } 204 | 205 | public func channelReadComplete(ctx: ChannelHandlerContext) { 206 | ctx.flush() 207 | } 208 | 209 | private func completeResponse(_ ctx: ChannelHandlerContext, trailers: HTTPHeaders?, promise: EventLoopPromise?) { 210 | //let promise = self.keepAlive ? promise : (promise ?? ctx.eventLoop.newPromise()) 211 | if !self.keepAlive { 212 | //promise!.futureResult.whenComplete { 213 | ctx.close(promise: nil) 214 | //} 215 | } 216 | //ctx.writeAndFlush(self.wrapOutboundOut(ByteBuffer.forString("0\r\n\r\n")), promise: promise) 217 | } 218 | 219 | 220 | // Solely for test purposes; remove before deployment 221 | private func printEnvVars(_ envVars: [String:String]) { 222 | let keys = envVars.keys.sorted() 223 | var startedHttp = false 224 | var finishedHttp = false 225 | var startedServer = false 226 | print("\nEnv Vars:") 227 | for key in keys { 228 | if !startedHttp && key.starts(with: "HTTP") { 229 | startedHttp = true 230 | print("") 231 | } 232 | if startedHttp && !finishedHttp && !key.starts(with: "HTTP") { 233 | finishedHttp = true 234 | print("") 235 | } 236 | else if finishedHttp && !startedServer && key.starts(with: "SERVER") { 237 | startedServer = true 238 | print("") 239 | } 240 | print("\(key)=\(envVars[key]!)") 241 | } 242 | } 243 | 244 | } 245 | 246 | 247 | func randomAlphaNumericString(length: Int = 7)->String{ 248 | 249 | enum s { 250 | static let c = Array("abcdefghjklmnpqrstuvwxyz12345789") 251 | static let k = UInt32(c.count) 252 | } 253 | 254 | var result = [Character](repeating: "a", count: length) 255 | 256 | for i in 0.. 1 else { 46 | SELogger.logUnexpectedCrash(stdOut) 47 | return 48 | } 49 | let responseCode = responseLine.components(separatedBy: " ")[1] 50 | 51 | guard components.count > 1 else { 52 | SELogger.logUnexpectedCrash(stdOut) 53 | return 54 | } 55 | let body = components[1] 56 | 57 | 58 | // Log the request 59 | SELogger.log(ip: ip, requestStr: "\(request.method) \(request.version) \(request.uri)", responseCode: responseCode, bodyLength: body.count) 60 | 61 | // If response code isn't 200, error 62 | if responseCode != "200" { 63 | let errorMsg = stdOut.replacingOccurrences(of: "\n", with: " ") 64 | SELogger.logError(ip: ip, errorMessage: errorMsg) 65 | } 66 | } 67 | 68 | // Logs an unexpected crash 69 | internal class func logUnexpectedCrash(_ str: String) { 70 | let str = "Unexpected crash with string: \(str)" 71 | SELogger.writeOut(str, toFile: SELogger.internalErrorLogName) 72 | } 73 | 74 | 75 | // Log a server request 76 | private class func log(ip: String, requestStr: String, responseCode code: String, bodyLength length: Int) { 77 | let str = "\(ip) - - [\(SELogger.getLogTime())] \"\(requestStr)\" \(code) \(length)\n" 78 | let file = SELogger.accessLogName 79 | SELogger.writeOut(str, toFile: file) 80 | } 81 | 82 | // Log a server error 83 | private class func logError(ip: String, errorMessage: String, logLevel: LogLevel = SELogger.defaultLogLevel) { 84 | let str = "[\(SELogger.getErrorTime())] [\(logLevel)] [client \(ip)] \(errorMessage)\n" 85 | let file = "\(logLevel).log" 86 | SELogger.writeOut(str, toFile: file) 87 | } 88 | 89 | 90 | // Generic writing to file 91 | private class func writeOut(_ str: String, toFile file: String) { 92 | let path = "\(SELogger.basePath)/\(file)" 93 | if let data = str.data(using: .utf8) { 94 | // File already exists 95 | if FileManager.default.fileExists(atPath: path) { 96 | if let fileHandle = FileHandle(forUpdatingAtPath: path) { 97 | let size = fileHandle.seekToEndOfFile() 98 | 99 | // Rotate logs if larger than max alloted size 100 | if size >= SELogger.maxLogSize { 101 | // Close this file handle as it will change, rotate, then call this function again 102 | fileHandle.closeFile() 103 | SELogger.rotateLogs() 104 | SELogger.writeOut(str, toFile: file) 105 | return 106 | } 107 | 108 | fileHandle.write(data) 109 | fileHandle.closeFile() 110 | } 111 | } 112 | // File does not exist 113 | else { 114 | let fileUrl = URL(fileURLWithPath: "\(path)") 115 | do { 116 | try str.write(to: fileUrl, atomically: false, encoding: .utf8) 117 | } 118 | catch { 119 | print("Could not write out to \(path)") 120 | exit(-1) 121 | } 122 | } 123 | } 124 | } 125 | 126 | // Rotates logs with the specified name 127 | private class func rotateLogs() { 128 | do { 129 | let fileManager = FileManager.default 130 | let allLogs = try fileManager.contentsOfDirectory(atPath: SELogger.basePath) 131 | let logTypes = [SELogger.accessLogName, SELogger.errorLogName] 132 | for name in logTypes { 133 | // Have the relevant logs in reverse sorted order (i.e: ..., access.log.1, access.log.0, access.log) so increment number by 1 134 | let logs = allLogs.filter({$0.starts(with: name)}).sorted().reversed() 135 | for file in logs { 136 | // Get log number of the file 137 | if let fileNumStr = file.chopPrefix("\(name)."), let fileNum = Int(fileNumStr) { 138 | try fileManager.moveItem(atPath: "\(SELogger.basePath)/\(file)", toPath: "\(SELogger.basePath)/\(name).\(fileNum+1)") 139 | } 140 | // Means we hit the currently active log; append 0 141 | else { 142 | try fileManager.moveItem(atPath: "\(SELogger.basePath)/\(file)", toPath: "\(SELogger.basePath)/\(name).0") 143 | } 144 | } 145 | } 146 | 147 | } 148 | catch { 149 | print("Error rotating logs") 150 | exit(0) 151 | } 152 | 153 | } 154 | 155 | 156 | // Construct string with time information for access.log entries 157 | private class func getLogTime() -> String { 158 | let now = Date() 159 | 160 | let day = SELogger.getDateComponentWithLengthTwo(.day, ofDate: now) 161 | let month = SELogger.getMonth(SELogger.cal.component(.month, from: now)) 162 | let year = SELogger.cal.component(.year, from: now) 163 | 164 | let hour = SELogger.getDateComponentWithLengthTwo(.hour, ofDate: now) 165 | let minute = SELogger.getDateComponentWithLengthTwo(.minute, ofDate: now) 166 | let second = SELogger.getDateComponentWithLengthTwo(.second, ofDate: now) 167 | 168 | let timezone = TimeZone.current.secondsFromGMT() / 60 / 60 169 | let timezoneStr: String 170 | if timezone < 10 && timezone >= 0 { 171 | timezoneStr = "0\(timezone)00" 172 | } 173 | else if timezone >= 10 { 174 | timezoneStr = "\(timezone)00" 175 | } 176 | else if timezone < 0 && timezone > -10 { 177 | timezoneStr = "-0\(abs(timezone))00" 178 | } 179 | else { 180 | timezoneStr = "-\(abs(timezone))00" 181 | } 182 | 183 | let dateStr = "\(day)/\(month)/\(year):\(hour):\(minute):\(second) \(timezoneStr)" 184 | return dateStr 185 | } 186 | 187 | private static func getErrorTime() -> String { 188 | let now = Date() 189 | 190 | let dayOfWeek = SELogger.getDay(SELogger.cal.component(.weekday, from: now)) 191 | let day = SELogger.getDateComponentWithLengthTwo(.day, ofDate: now) 192 | let month = SELogger.getMonth(SELogger.cal.component(.month, from: now)) 193 | 194 | let hour = SELogger.getDateComponentWithLengthTwo(.hour, ofDate: now) 195 | let minute = SELogger.getDateComponentWithLengthTwo(.minute, ofDate: now) 196 | let second = SELogger.getDateComponentWithLengthTwo(.second, ofDate: now) 197 | 198 | let timezone = TimeZone.current.secondsFromGMT() / 60 / 60 199 | let timezoneStr: String 200 | if timezone < 10 && timezone >= 0 { 201 | timezoneStr = "0\(timezone)00" 202 | } 203 | else if timezone >= 10 { 204 | timezoneStr = "\(timezone)00" 205 | } 206 | else if timezone < 0 && timezone > -10 { 207 | timezoneStr = "-0\(abs(timezone))00" 208 | } 209 | else { 210 | timezoneStr = "-\(abs(timezone))00" 211 | } 212 | 213 | let dateStr = "\(dayOfWeek) \(month) \(day) \(hour):\(minute):\(second) \(timezoneStr)" 214 | return dateStr 215 | } 216 | 217 | 218 | // Helper functions for getting pieces of date strings 219 | private static func getDateComponentWithLengthTwo(_ dc: Calendar.Component, ofDate date: Date) -> String { 220 | let component = SELogger.cal.component(dc, from: date) 221 | if component < 10 { 222 | return "0\(component)" 223 | } 224 | return "\(component)" 225 | } 226 | 227 | private static func getDay(_ dayInt: Int) -> String { 228 | switch dayInt { 229 | case 1: return "Sun" 230 | case 2: return "Mon" 231 | case 3: return "Tues" 232 | case 4: return "Wed" 233 | case 5: return "Thur" 234 | case 6: return "Fri" 235 | case 7: return "Sat" 236 | default: return "Unknown" 237 | } 238 | } 239 | 240 | private static func getMonth(_ monthInt: Int) -> String { 241 | switch monthInt { 242 | case 1: return "Jan" 243 | case 2: return "Feb" 244 | case 3: return "Mar" 245 | case 4: return "Apr" 246 | case 5: return "May" 247 | case 6: return "Jun" 248 | case 7: return "Jul" 249 | case 8: return "Aug" 250 | case 9: return "Sep" 251 | case 10: return "Oct" 252 | case 11: return "Nov" 253 | case 12: return "Dec" 254 | default: return "Unknown" 255 | } 256 | } 257 | 258 | } 259 | 260 | -------------------------------------------------------------------------------- /Sources/SwiftEngineServer/SEMiddlewareHandler.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import NIO 3 | import NIOHTTP1 4 | 5 | public enum SEHTTPDataPart { 6 | case request(RequestT) 7 | case response(ResponseT) 8 | case end() 9 | } 10 | 11 | extension SEHTTPDataPart: Equatable { 12 | public static func ==(lhs: SEHTTPDataPart, rhs: SEHTTPDataPart) -> Bool { 13 | switch (lhs, rhs) { 14 | case (.request(let h1), .request(let h2)): 15 | return h1 == h2 16 | case (.response(let b1), .response(let b2)): 17 | return b1 == b2 18 | case (.end(let h1), .end(let h2)): 19 | return h1 == h2 20 | case (.request, _), (.response, _), (.end, _): 21 | return false 22 | } 23 | } 24 | } 25 | 26 | 27 | public typealias SEHTTPOutboundDataPart = SEHTTPDataPart 28 | 29 | public struct SEHTTPDataRequestHeadPart: Equatable { 30 | public var headers: HTTPHeaders 31 | } 32 | 33 | 34 | ////////////////////////// 35 | 36 | class SEMiddlewareHandler: ChannelOutboundHandler { 37 | public typealias OutboundIn = ByteBuffer 38 | public typealias OutboundOut = ByteBuffer 39 | 40 | public func handlerAdded(ctx: ChannelHandlerContext) { 41 | 42 | } 43 | 44 | public func channelActive(ctx: ChannelHandlerContext) { 45 | track() 46 | ctx.fireChannelActive() 47 | } 48 | 49 | public func channelInactive(ctx: ChannelHandlerContext) { 50 | track() 51 | ctx.fireChannelInactive() 52 | } 53 | 54 | 55 | public func requestHeadDecoded(ctx: ChannelHandlerContext){ 56 | 57 | 58 | } 59 | 60 | 61 | public func write(ctx: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise?) { 62 | track() 63 | let binaryData = unwrapOutboundIn(data) 64 | let str = binaryData.getString(at: 0, length: binaryData.readableBytes) 65 | print("writeData:: \(String(describing: str))") 66 | ctx.write(data, promise: promise) 67 | } 68 | 69 | public func writeAndFlush(ctx: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise?) { 70 | track() 71 | ctx.writeAndFlush(data, promise: promise) 72 | } 73 | 74 | public func flush(ctx: ChannelHandlerContext) { 75 | track() 76 | ctx.flush() 77 | } 78 | 79 | public func close(ctx: ChannelHandlerContext, mode: CloseMode, promise: EventLoopPromise?) { 80 | track() 81 | var string = "HTTP/1.1 200 OK\ncontent-length: 12\n\nHello World!" 82 | var buf = ctx.channel.allocator.buffer(capacity: string.utf8.count) 83 | buf.set(string: string, at: 0) 84 | let nio1 = NIOAny(ByteBuffer.forString(string)) //NIOAny(buf) 85 | print("NIOAny :: \(nio1)") 86 | ctx.writeAndFlush(nio1, promise: nil) 87 | //ctx.write(nio1, promise: nil) 88 | print("\tend\n") 89 | ctx.close(promise: promise) 90 | } 91 | 92 | 93 | } 94 | -------------------------------------------------------------------------------- /Sources/SwiftEngineServer/SEShell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SEClass.swift 3 | // HTTPTest 4 | // 5 | // Created by Brandon Holden on 7/20/18. 6 | // Copyright © 2018 Brandon Holden. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class SEShell{ 12 | @discardableResult 13 | public class func run(_ args: String...) -> (stdOut : String, stdErr : String, status : Int32) { 14 | return run(args) 15 | } 16 | @discardableResult 17 | public class func run(_ args: [String], envVars: [String : String]? = nil ) -> (stdOut : String, stdErr : String, status : Int32) { 18 | //let fm = FileManager.default 19 | //let pwd = fm.currentDirectoryPath 20 | #if swift(>=3.1) 21 | let task = Process() 22 | #else 23 | let task = Task() 24 | #endif 25 | let pipeStdOut = Pipe() 26 | let pipeStdErr = Pipe() 27 | task.standardOutput = pipeStdOut 28 | task.standardError = pipeStdErr 29 | //var envShell = ProcessInfo.processInfo.environment 30 | task.arguments = args 31 | task.environment = envVars 32 | #if os(OSX) 33 | if #available(OSX 10.13, *) { 34 | task.executableURL = URL(fileURLWithPath: "/usr/bin/env") 35 | try! task.run() 36 | } 37 | else { 38 | task.launchPath = "/usr/bin/env" 39 | task.launch() 40 | } 41 | #elseif os(Linux) 42 | task.launchPath = "/usr/bin/env" 43 | task.launch() 44 | #endif 45 | task.waitUntilExit() 46 | let dataStdOut = pipeStdOut.fileHandleForReading.readDataToEndOfFile() 47 | let stdOut = String(data: dataStdOut, encoding: String.Encoding.utf8) ?? "" 48 | //let stdOut = output.replacingOccurrences(of: "\n", with: "", options: .literal, range: nil) 49 | let dataStdErr = pipeStdErr.fileHandleForReading.readDataToEndOfFile() 50 | let stdErr = String(data: dataStdErr, encoding: String.Encoding.utf8) ?? "" 51 | let status = task.terminationStatus 52 | return (stdOut, stdErr, status) 53 | } 54 | 55 | @discardableResult 56 | public class func bash(_ cmd: String) -> 57 | (stdOut : String, stdErr : String, status : Int32) { 58 | //let fm = FileManager.default 59 | //let pwd = fm.currentDirectoryPath 60 | #if swift(>=3.1) 61 | let task = Process() 62 | #else 63 | let task = Task() 64 | #endif 65 | // uncomment lines below to pass through the env 66 | //var envShell = ProcessInfo.processInfo.environment 67 | //task.environment = envShell 68 | // create a pipe for capturing the output from shell 69 | let pipeStdOut = Pipe() 70 | let pipeStdErr = Pipe() 71 | task.standardOutput = pipeStdOut 72 | task.standardError = pipeStdErr 73 | task.arguments = ["/bin/bash","-c", cmd]//args 74 | #if os(OSX) 75 | if #available(OSX 10.13, *) { 76 | task.executableURL = URL(fileURLWithPath: "/usr/bin/env") 77 | try! task.run() 78 | } 79 | else { 80 | task.launchPath = "/usr/bin/env" 81 | task.launch() 82 | } 83 | #elseif os(Linux) 84 | task.launchPath = "/usr/bin/env" 85 | task.launch() 86 | #endif 87 | task.waitUntilExit() 88 | let dataStdOut = pipeStdOut.fileHandleForReading.readDataToEndOfFile() 89 | let stdOut = String(data: dataStdOut, encoding: String.Encoding.utf8) ?? "" 90 | //let stdOut = output.replacingOccurrences(of: "\n", with: "", options: .literal, range: nil) 91 | let dataStdErr = pipeStdErr.fileHandleForReading.readDataToEndOfFile() 92 | let stdErr = String(data: dataStdErr, encoding: String.Encoding.utf8) ?? "" 93 | let status = task.terminationStatus 94 | return (stdOut, stdErr, status) 95 | //return task.terminationStatus 96 | } 97 | public class func runBinary(_ fileUri : String){ 98 | // first close all the current piple 99 | // let stdin = FileHandle.standardInput 100 | // let stdout = FileHandle.standardOutput 101 | // let stderror = FileHandle.standardError 102 | //int fds[2]; 103 | //let fds = [UnsafeMutablePointer!](repeating: nil, count: 64) 104 | // var fds: [Int32] = [-1, -1] 105 | // 106 | // var pipe_in: [Int32] = [-1, -1] 107 | // var pipe_out: [Int32] = [-1, -1] 108 | // var pipe_err: [Int32] = [-1, -1] 109 | // 110 | // pipe(&pipe_in) 111 | // pipe(&pipe_out) 112 | // pipe(&pipe_err) 113 | //close(pipe_in[1]); 114 | //close(pipe_out[0]); 115 | //close(pipe_err[0]); 116 | // dup2(pipe_in[0], 0); 117 | // dup2(pipe_out[1], 1); 118 | // dup2(pipe_err[1], 2); 119 | // close(pipe_in[0]); 120 | // close(pipe_out[1]); 121 | // close(pipe_err[1]); 122 | //pipe(&fds) 123 | //close(STDIN_FILENO) 124 | //dup2(fds[0], STDIN_FILENO) 125 | //close(fds[1]); 126 | //dup2(fds[0], STDIN_FILENO); 127 | //stdin.closeFile() 128 | //close(STDIN_FILENO) 129 | //stdout.closeFile() 130 | //stderror.closeFile() 131 | let args = [fileUri] 132 | // Array of UnsafeMutablePointer 133 | let cargs = args.map { strdup($0) } + [nil] 134 | //exec(fileUri) 135 | execv(fileUri, cargs) 136 | //execvp(fileUri, cargs) 137 | //execl(fileUri, "main") 138 | } 139 | public class func runBinary(){ 140 | let args = ["ls", "-l", "/Library"] 141 | // Array of UnsafeMutablePointer 142 | let cargs = args.map { strdup($0) } + [nil] 143 | execv("/bin/ls", cargs) 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /Sources/SwiftEngineServer/SocketAddress+IP.swift: -------------------------------------------------------------------------------- 1 | import NIO 2 | 3 | 4 | extension SocketAddress { 5 | var ip: String? { 6 | let ipSegment = "\(self)".split(separator: "]") 7 | if ipSegment.count > 1 { 8 | return ipSegment[1].components(separatedBy: ":")[0] 9 | } 10 | return nil 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/SwiftEngineServer/main.swift: -------------------------------------------------------------------------------- 1 | import NIO 2 | import NIOHTTP1 3 | 4 | // First argument is the program path 5 | var arguments = CommandLine.arguments.dropFirst(0) // just to get an ArraySlice from [String] 6 | var allowHalfClosure = true 7 | if arguments.dropFirst().first == .some("--disable-half-closure") { 8 | allowHalfClosure = false 9 | arguments = arguments.dropFirst() 10 | } 11 | let arg1 = arguments.dropFirst().first 12 | let arg2 = arguments.dropFirst().dropFirst().first 13 | let arg3 = arguments.dropFirst().dropFirst().dropFirst().first 14 | 15 | let defaultHost = "0.0.0.0" //"::1" 16 | let defaultPort = 8887 17 | let defaultHtdocs = "/dev/null/" 18 | 19 | enum BindTo { 20 | case ip(host: String, port: Int) 21 | case unixDomainSocket(path: String) 22 | } 23 | 24 | let htdocs: String 25 | let bindTarget: BindTo 26 | 27 | switch (arg1, arg1.flatMap(Int.init), arg2, arg2.flatMap(Int.init), arg3) { 28 | case (.some(let h), _ , _, .some(let p), let maybeHtdocs): 29 | /* second arg an integer --> host port [htdocs] */ 30 | bindTarget = .ip(host: h, port: p) 31 | htdocs = maybeHtdocs ?? defaultHtdocs 32 | case (_, .some(let p), let maybeHtdocs, _, _): 33 | /* first arg an integer --> port [htdocs] */ 34 | bindTarget = .ip(host: defaultHost, port: p) 35 | htdocs = maybeHtdocs ?? defaultHtdocs 36 | case (.some(let portString), .none, let maybeHtdocs, .none, .none): 37 | /* couldn't parse as number --> uds-path [htdocs] */ 38 | bindTarget = .unixDomainSocket(path: portString) 39 | htdocs = maybeHtdocs ?? defaultHtdocs 40 | default: 41 | htdocs = defaultHtdocs 42 | bindTarget = BindTo.ip(host: defaultHost, port: defaultPort) 43 | } 44 | 45 | let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) 46 | let threadPool = BlockingIOThreadPool(numberOfThreads: 6) 47 | threadPool.start() 48 | 49 | class CustomResponseHandler : ChannelHandler, ChannelOutboundHandler { 50 | public typealias OutboundIn = ByteBuffer 51 | public typealias OutboundOut = ByteBuffer 52 | 53 | } 54 | 55 | let fileIO = NonBlockingFileIO(threadPool: threadPool) 56 | let bootstrap = ServerBootstrap(group: group) 57 | // Specify backlog and enable SO_REUSEADDR for the server itself 58 | .serverChannelOption(ChannelOptions.backlog, value: 256) 59 | .serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1) 60 | 61 | // Set the handlers that are applied to the accepted Channels 62 | .childChannelInitializer { channel in 63 | 64 | 65 | channel.pipeline.add(handler: HTTPRequestDecoder(leftOverBytesStrategy: .dropBytes)).then{ 66 | channel.pipeline.add(handler: SEHTTPHandler(fileIO: fileIO, htdocsPath: htdocs)) 67 | } 68 | 69 | 70 | //channel.pipeline.add(handler: SEMiddlewareHandler()).then{ 71 | // channel.pipeline.configureHTTPServerPipeline(first: false, 72 | // withPipeliningAssistance: true, 73 | // withServerUpgrade: nil, 74 | // withErrorHandling: false, 75 | // withCustomResponseHandler: CustomResponseHandler()).then { 76 | // channel.pipeline.add(handler: SEHTTPHandler(fileIO: fileIO, htdocsPath: htdocs)) 77 | // } 78 | //s} 79 | } 80 | 81 | // Enable TCP_NODELAY and SO_REUSEADDR for the accepted Channels 82 | .childChannelOption(ChannelOptions.socket(IPPROTO_TCP, TCP_NODELAY), value: 1) 83 | .childChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1) 84 | .childChannelOption(ChannelOptions.maxMessagesPerRead, value: 1) 85 | .childChannelOption(ChannelOptions.allowRemoteHalfClosure, value: allowHalfClosure) 86 | 87 | defer { 88 | try! group.syncShutdownGracefully() 89 | try! threadPool.syncShutdownGracefully() 90 | } 91 | 92 | print("htdocs = \(htdocs)") 93 | 94 | let channel = try { () -> Channel in 95 | switch bindTarget { 96 | case .ip(let host, let port): 97 | return try bootstrap.bind(host: host, port: port).wait() 98 | case .unixDomainSocket(let path): 99 | return try bootstrap.bind(unixDomainSocketPath: path).wait() 100 | } 101 | }() 102 | 103 | guard let localAddress = channel.localAddress else { 104 | fatalError("Address was unable to bind. Please check that the socket was not closed or that the address family was understood.") 105 | } 106 | print("Server started and listening on \(localAddress), htdocs path \(htdocs)") 107 | 108 | // This will never unblock as we don't close the ServerChannel 109 | try channel.closeFuture.wait() 110 | 111 | print("Server closed") 112 | 113 | 114 | public func track(_ message: String = "", file: String = #file, function: String = #function, line: Int = #line ) { 115 | print("*** called from \(function) \(file):\(line) :: \(message) ") 116 | } 117 | -------------------------------------------------------------------------------- /TechnicalOverview.md: -------------------------------------------------------------------------------- 1 | # SwiftEngine Design Overview 2 | 3 | SwiftEngine is aimed at being a highly resilient and scalable server-side platform, and as such the goal of the platform is to overcome some of the inherent vulnerabilities of other modern platforms currently in the market. A key example of what this means for production deployments is that each request and endpoint is contained within its own process; a crash for one request does not bring down the entire server. Similarly, memory leaks or other bugs are ephemeral and only live for the duration of the request, instead of transcending the lifetime of the server. 4 | 5 | In addition to being a resilient and scalable, SwiftEngine is also highly performant due to the pre-compiled nature of all the endpoints because under the hood SwiftEngine uses Swift as its core language. All code that is executed on the server is a compiled (native object code) version of the source code. Thus, no processing power is spent on maintain a runtime environment. 6 | 7 | 8 | All the developer has to do is save their .swift files within the designated site directory and SwiftEngine will handle the rest. Any compile time and runtime errors are handled by SwiftEngine, and are provided to the developer in a beautiful and easy to follow interface. With SwiftEngine, long gone are the days of manual compilation and dealing with shell log dumps. Simply save a file and request the URL, and SwiftEngine will automatically compile, cache, and serve the requested results. 9 | 10 | In order to achieve these goals, there is a slight paradigm change compared to how a typical Swift project functions. The primary difference is the independently asynchronous processing of the various endpoints. Each one of the endpoints are compiled and maintained on their own; the introduction of a bug within one endpoint has no effect on the functionality of the rest of the site, but rather it's contained to that specific endpoint only. 11 | 12 | 13 | ## Routing Logic 14 | 15 | One of the root concepts in SwiftEngine is its sophisticated routing logic. Out of the box, SwiftEngine has a smart routing logic that automatically attempts to match the http requests to a specific .swift file as its endpoint. However, for more advanced usage, a Router.swift file can be included within the root folder of the site for close control and customization of the request handling of the server. 16 | 17 | ![N|Solid](http://www.plantuml.com/plantuml/svg/bP5FSzem4CNl-XGxES4bt4dR96tAj8Sc6K8_W2gjiH9Bwqgh05FuxaKMs9v0_i01nEhjy_izNmMMdgEjH7WohfPUMf2ApRFX5VmJ05-bUffxYav_eueyB4h3cERaDVey-rDjHGBWnaXBJXzTWUx-sEgrzxJeZpQYbsZxpOODkHofjp_tSjLe4uOO_vZDxc6AVoC6tlugDC-enw9ahiUZaIOhZJjNP4UVS2bjNU6N2s4A64mfClhpi305Gs4g15oQmE5o25oYWy4Amr10ByZPuKf5SwFEceW0oVMNP5NkdA0W3pudU-cQFeFl-vD6bgfi_c0LC1dz3E2Rnu4bZV0P0dkZ68uQJLunWe6ZV9L7Jfj206n4TodoKFGmhoEJbMmKPE34b-dsDIyCbnJRsXXTCEGEagMLjlN3fa_kfFKQQInah3XxdcgNYJjRdwbGvt2b392DEpIuF0gd2GFirk4btqbuyDQhp87VJYmTSOc-5tbGigtMZ_LyNRlv-Z6GMoEhuGTGKB-7yV0AniSeyc4Zacmd429LCxxtf30a6WsuH1KPstuVsdx3XGXCjH0fntYA7BrB8I6mPACQJIkPT9BRknbCxdxVta39N3Vg642HtgklqEbeg_y0) 18 | 19 | As an example for the automatic routing when an extension is not specified, consider the following URL: `http://somedomain.com/componenet1/component2/componenet3/componenet4` 20 | 21 | Since a Swift file can serve as the base executable in various positions within the URL path, the automatic URL router will use the following logic, and select the first available in the following order. 22 | 23 | ![N|Solid](http://www.plantuml.com/plantuml/svg/pPFFRjGm4CRlVefHJzaSkkBFAJtGGgaujTMoF82n9yseYITunbrMLTyTEsp1GdlWK24i9R4_V-FVvrW-5xLHqpGQkQSmssWdi4xfWNGFZmRWlNNtTv5Jy1zuv0YxWHIBpj5Z_Abz7RCfQvTa9mx-Q0bKyqTABsBaNIqzcIfHVYifSO37Fz1tqUwBTzc6wJvjjxUmmMy9HVFN8JsWxyoWEdVhjV24dYTBuTJnjCxFp043wgjVVNrToM-g_jipOypl72SNIVDAIWusd1GZD86Xfn41loi6AIQPqL5Fw5SdIt3geQcfRNigE-grRPyhILJUhK073D3iKLO6bjPyVEvvLEk6FC1Hi43BH1a6Jxqv0gcszgzvzQzySVRVe-jJ_4zYhDlMOs_Jg2yIfpgG9zDShSp1OqmfwsuZEnG16tmce4kA40-Nv7F1Bt0vCw8yczpA4jq6DSK0rpTRkBhvSdq9vHQ1gDKauDZZmN-UoMaQVm00) 24 | 25 | ## SwiftEngine Compiler Processor Logic 26 | 27 | ### `require` directive 28 | In order to maintain resiliency and endpoint independence, SwiftEngine takes a slightly different approach in how individual files are compiled. With a typical Swift based project, all the files are compiled together, and as a result the entire app either works or it does not. Conversely, within SwiftEngine, each one of the requested endpoints is compiled independently, so a bug introduced in one area of the code does not affect any other endpoint. In order to achieve this, the developer needs to specify (within each file) any other files within SwiftEngine that need to be used within that specific Swift file. The SEProcessor will iteratively process all the files with the `require` directive, so the developer only needs to specify what other Swift files are needed within the first Swift file. 29 | 30 | As an example, if the example1.swift file depends on a class that is declared within example2.swift, the developer will need to specify example2.swift as a dependency via our custom `require` directive. To do so, one must only add the following line to example1.swift: 31 | `//se: require directory/example2.swift` 32 | 33 | This will inform SEProcessor that the example2.swift file should be used along with example1.swift file during the compilation process. 34 | 35 | The following is a high-level logic for SwiftEngine preprocessor `require` directives: 36 | 37 | ![N|Solid](http://www.plantuml.com/plantuml/svg/RL9DRzim3BthL_3e14C3xEWC_UY6dOVj5p0HdH2LHLUIcsN3_lj8sQWJD08IIoJVu-EJ7dF1LCO-kFp2SS24FU2-y1kNC_nr0C-uVpaa6QF_Aa4Id8vSoEAIFAKfjWjQfB5lZBr4VnC2I_uMz2abELs6_haBHVfVkB34AkaIVqXuhhKsXcj_91gIx2bdpVso6FcjQpag6WF_8LYl4xsEd0W2vx9U0oQbs6GgMzSyhPodWlkZg_is4Nh89-uAT1n8cJgE7Z24XKSfl7xCCygjsoXL2tCEilJqNOBAtT1lx8T0d-ygtzOvxg2vpGATcVUmWdAa2CsPyie1w6Y4nO4kWtvPkEytqu43tK_qAj6qdwjp8DsLN1lyYhjoKiW4JHHhPQj5xw781yEsSzuCcdPH2a7Ymz74JUjdrMezhZgRYTaqPOu7c6yA9C8xaDHy3P1fDZJtWzWbJj1FWD4lnJroBFghbMFar_7M_UO5OCI783iCDNit9oZwEeIR3zpfpis6s_CDwamlwNgOH1qbYZe2u1jom5sW1pLpCT9DNAvvNjwLxTi1SvxDE74vSlmekyZyyFRI3rgPN6EbTTGaKYhSXhE06L43gwtBHGRtg9t7Flm7) 38 | 39 | 40 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | XCTMain([ 4 | 5 | ]) -------------------------------------------------------------------------------- /Tests/SEProcessorLibTests/SEProcessirLibTests.swift: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swiftengine/SwiftEngine/8cdc39859492fdf810db8a1b2dfa142bd97c3042/Tests/SEProcessorLibTests/SEProcessirLibTests.swift -------------------------------------------------------------------------------- /Tests/SEProcessorTests/SEProcessorTests.swift: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swiftengine/SwiftEngine/8cdc39859492fdf810db8a1b2dfa142bd97c3042/Tests/SEProcessorTests/SEProcessorTests.swift -------------------------------------------------------------------------------- /Tests/SwiftEngineServerTests/SwiftEngineTests.swift: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swiftengine/SwiftEngine/8cdc39859492fdf810db8a1b2dfa142bd97c3042/Tests/SwiftEngineServerTests/SwiftEngineTests.swift -------------------------------------------------------------------------------- /bin/se: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [[ $EUID -ne 0 ]]; then 3 | echo "This script must be run as root" 4 | exit 1 5 | fi 6 | 7 | testcmd () { 8 | command -v "$1" >/dev/null 9 | } 10 | 11 | if [[ "$OSTYPE" == "linux-gnu" ]]; then 12 | if testcmd make; then 13 | make install-dependencies 14 | else 15 | echo "WARNING!" 16 | echo "This project requires build-essentails tools to be installed." 17 | echo "Use following command to perform installation: sudo apt-get -y install build-essentials" 18 | fi 19 | elif [[ "$OSTYPE" == "darwin"* ]]; then 20 | if testcmd make; then 21 | make install-dependencies 22 | elif testcmd xcode-select; then 23 | echo "WARNING!" 24 | echo "It looks like you have Xcode installed in your system, but you don't have the Command Line Tools option installed yet." 25 | echo "Please Install Command Line Tools in Mac OS X before proceeeding." 26 | echo "Use following command to perform installation: xcode-select --install" 27 | else 28 | echo "WARNING!" 29 | echo "This project requires Apple developer tools to be installed." 30 | echo "Please install Xcode or Apple Command Line Tools before proceeding." 31 | fi 32 | 33 | fi 34 | 35 | 36 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | make run --------------------------------------------------------------------------------