Skip to content

Instantly share code, notes, and snippets.

@zqqf16
Forked from sciolist/CorsProxy.swift
Created January 22, 2017 08:54
Show Gist options
  • Save zqqf16/fc1b2f37eead98ec44b5bc682b07796b to your computer and use it in GitHub Desktop.
Save zqqf16/fc1b2f37eead98ec44b5bc682b07796b to your computer and use it in GitHub Desktop.
GCDWebServer Cors proxy
import GCDWebServer
let webserver = GCDWebServer()
let proxy = CorsProxy(webserver: webserver!, urlPrefix: "/CORS/")
webserver.startWithPort(8080, bonjourName: nil)
// $ curl -i -H "Origin: test-site" http://127.0.0.1/CORS/http://my/test/site
// HTTP/1.1 200 OK
// Access-Control-Allow-Origin: test-site
// Server: GCDWebServer
// Access-Control-Allow-Credentials: true
// Connection: Close
// Access-Control-Allow-Methods: PUT,POST,GET,PATCH,DELETE
// Cache-Control: no-cache
import GCDWebServer
class CorsProxy {
init(webserver : GCDWebServer!, urlPrefix: String) {
var prefix =
(urlPrefix.hasPrefix("/") ? "" : "/")
+ urlPrefix
+ (urlPrefix.hasSuffix("/") ? "" : "/")
let pattern = "^" + NSRegularExpression.escapedPatternForString(prefix) + ".*"
webserver.addHandlerForMethod("POST", pathRegex: pattern, requestClass: GCDWebServerDataRequest.self, processBlock:{ req in
return self.sendProxyResult(prefix, req: req)
})
webserver.addHandlerForMethod("PUT", pathRegex: pattern, requestClass: GCDWebServerDataRequest.self, processBlock:{ req in
return self.sendProxyResult(prefix, req: req)
})
webserver.addHandlerForMethod("PATCH", pathRegex: pattern, requestClass: GCDWebServerDataRequest.self, processBlock:{ req in
return self.sendProxyResult(prefix, req: req)
})
webserver.addHandlerForMethod("DELETE", pathRegex: pattern, requestClass: GCDWebServerDataRequest.self, processBlock:{ req in
return self.sendProxyResult(prefix, req: req)
})
webserver.addHandlerForMethod("GET", pathRegex: pattern, requestClass: GCDWebServerRequest.self, processBlock:{ req in
return self.sendProxyResult(prefix, req: req)
})
webserver.addHandlerForMethod("OPTIONS", pathRegex: pattern, requestClass: GCDWebServerRequest.self, processBlock:{ req in
return self.sendCorsHeaders(prefix, req: req)
})
}
func sendProxyResult(prefix: String, req: GCDWebServerRequest) -> GCDWebServerResponse! {
let query = req.URL.query == nil ? "" : "?" + req.URL.query!
var url = NSURL(string: req.path.substringFromIndex(prefix.endIndex) + query)
if (url == nil) { return sendError("Invalid URL") }
var err: NSErrorPointer = nil
var urlResp: NSURLResponse?
let urlReq = NSMutableURLRequest(URL: url!, cachePolicy: NSURLRequestCachePolicy.ReloadIgnoringLocalCacheData, timeoutInterval: 320000)
urlReq.HTTPMethod = req.method
urlReq.allHTTPHeaderFields = req.headers
urlReq.allHTTPHeaderFields?["Host"] = url!.host
if (req.hasBody()) {
urlReq.HTTPBody = (req as! GCDWebServerDataRequest).data
}
let output = NSURLConnection.sendSynchronousRequest(urlReq
, returningResponse: &urlResp
, error: err)
if (urlResp == nil) {
return sendError(output == nil ? nil : NSString(data: output!, encoding: NSUTF8StringEncoding) as? String);
}
var httpResponse = urlResp as! NSHTTPURLResponse
let resp = GCDWebServerDataResponse(data: output == nil ? NSData() : output, contentType: "application/x-unknown")
resp.statusCode = httpResponse.statusCode
for key in httpResponse.allHeaderFields {
if (toString(key.0) == "Content-Encoding") { continue; }
resp.setValue(toString(key.1), forAdditionalHeader: toString(key.0))
}
if (output != nil) {
resp.setValue(String(output!.length), forAdditionalHeader: "Content-Length")
}
return resp
}
func sendCorsHeaders(prefix: String, req: GCDWebServerRequest) -> GCDWebServerResponse! {
let resp = GCDWebServerResponse()
resp.setValue(toString(req.headers["Origin"]), forAdditionalHeader: "Access-Control-Allow-Origin")
resp.setValue("PUT,POST,GET,PATCH,DELETE", forAdditionalHeader: "Access-Control-Allow-Methods")
resp.setValue("true", forAdditionalHeader: "Access-Control-Allow-Credentials")
return resp
}
func sendError(error: String?) -> GCDWebServerResponse! {
var msg = error == nil ? "An error occured" : error!
let errorData = msg.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)
let resp = GCDWebServerDataResponse(data: errorData, contentType: "text/plain")
resp.statusCode = 500
return resp
}
func toString(v: AnyObject?) -> String! {
if (v == nil) { return ""; }
return String(stringInterpolationSegment: v!)
}
}
@shirakaba
Copy link

Here's a Swift 4 (not Swift 4.2, sadly) direct translation of the main part (no improvements like migrating sync methods to async ones; and a couple of warnings about deprecated methods) that I've just briefly tested for GET a request to https://www.bbc.co.uk. It appears to work!

import Foundation
import GCDWebServer

/* https://gist.github.com/zqqf16/fc1b2f37eead98ec44b5bc682b07796b https://gist.github.com/sciolist/2e741ff651ffe58b28f4 */
class CorsProxy {
    var webServer: GCDWebServer
    
    init(webserver: GCDWebServer, urlPrefix: String) {
        self.webServer = webserver
        
        let prefix =
            (urlPrefix.hasPrefix("/") ? "" : "/")
                + urlPrefix
                + (urlPrefix.hasSuffix("/") ? "" : "/")
        
        let pattern = "^" + NSRegularExpression.escapedPattern(for: prefix) + ".*"
        
        webserver.addHandler(forMethod: "POST", pathRegex: pattern, request: GCDWebServerDataRequest.self, processBlock:{ req in
            return self.sendProxyResult(prefix, req: req)
        })
        
        webserver.addHandler(forMethod: "PUT", pathRegex: pattern, request: GCDWebServerDataRequest.self, processBlock:{ req in
            return self.sendProxyResult(prefix, req: req)
        })
        
        webserver.addHandler(forMethod: "PATCH", pathRegex: pattern, request: GCDWebServerDataRequest.self, processBlock:{ req in
            return self.sendProxyResult(prefix, req: req)
        })
        
        webserver.addHandler(forMethod: "DELETE", pathRegex: pattern, request: GCDWebServerDataRequest.self, processBlock:{ req in
            return self.sendProxyResult(prefix, req: req)
        })
        
        webserver.addHandler(forMethod: "GET", pathRegex: pattern, request: GCDWebServerRequest.self, processBlock:{ req in
            return self.sendProxyResult(prefix, req: req)
        })
        
        webserver.addHandler(forMethod: "OPTIONS", pathRegex: pattern, request: GCDWebServerRequest.self, processBlock:{ req in
            return self.sendCorsHeaders(prefix, req: req)
        })
    }
    
    func sendProxyResult(_ prefix: String, req: GCDWebServerRequest) -> GCDWebServerResponse? {
        let query = req.url.query == nil ? "" : "?" + req.url.query!
        let url = URL(string: req.path.substring(from: prefix.endIndex) + query)
        if (url == nil) { return sendError(error: "Invalid URL") }
        
        var urlResp: URLResponse?
        var urlReq = URLRequest(url: url!, cachePolicy: NSURLRequest.CachePolicy.reloadIgnoringLocalCacheData, timeoutInterval: 320000)
        urlReq.httpMethod = req.method
        urlReq.allHTTPHeaderFields = req.headers as? [String : String]
        urlReq.allHTTPHeaderFields?["Host"] = url!.host
        
        if (req.hasBody()) {
            urlReq.httpBody = (req as! GCDWebServerDataRequest).data
        }
        
        do {
            let output = try NSURLConnection.sendSynchronousRequest(urlReq, returning: &urlResp)
            
            if (urlResp == nil) {
                
                return sendError(error: String(data: output, encoding: String.Encoding.utf8));
            }
            
            let httpResponse = urlResp as! HTTPURLResponse
            let resp = GCDWebServerDataResponse(data: output, contentType: "application/x-unknown")
            resp.statusCode = httpResponse.statusCode
            for key in httpResponse.allHeaderFields {
                if (toString(v: key.0 as AnyObject) == "Content-Encoding") { continue; }
                resp.setValue(toString(v: key.1 as AnyObject), forAdditionalHeader: toString(v: key.0 as AnyObject))
            }
            resp.setValue(String(output.count), forAdditionalHeader: "Content-Length")
            return resp
        } catch {
            print("Proxy error: ", error.localizedDescription)
            return nil
        }
    }
    
    func sendCorsHeaders(_ prefix: String, req: GCDWebServerRequest) -> GCDWebServerResponse! {
        let resp = GCDWebServerResponse()
        resp.setValue(toString(v: req.headers["Origin"] as AnyObject), forAdditionalHeader: "Access-Control-Allow-Origin")
        resp.setValue("PUT,POST,GET,PATCH,DELETE", forAdditionalHeader: "Access-Control-Allow-Methods")
        resp.setValue("true", forAdditionalHeader: "Access-Control-Allow-Credentials")
        return resp
    }
    
    func sendError(error: String?) -> GCDWebServerResponse! {
        let msg = error == nil ? "An error occured" : error!
        let errorData = msg.data(using: String.Encoding.utf8, allowLossyConversion: true)
        let resp = GCDWebServerDataResponse(data: errorData!, contentType: "text/plain")
        resp.statusCode = 500
        return resp
    }
    
    func toString(v: AnyObject?) -> String! {
        if (v == nil) { return ""; }
        return String(stringInterpolationSegment: v!)
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment