Skip to content

Instantly share code, notes, and snippets.

@sciolist
Created July 8, 2015 08:07
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save sciolist/2e741ff651ffe58b28f4 to your computer and use it in GitHub Desktop.
Save sciolist/2e741ff651ffe58b28f4 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!)
}
}
@anoop4real
Copy link

Swift 3 version for this?

@shirakaba
Copy link

shirakaba commented Oct 30, 2018

@anoop4real 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!)
    }
}

@troyanskiy
Copy link

Hello @shirakaba and @sciolist
Why do you have so big timeintervals here timeoutInterval: 320000?
Isn't 88 hours timeout too big?

Thanks!

@shirakaba
Copy link

@troyanskiy I'm afraid I can't really comment on any of the implementation/design decisions (and I don't pretend to be an expert on proxying), as I merely translated the code to Swift 4.

I would however agree that 88 hours does seem rather excessive for a network timeout; by contrast, web browsers frequently use around 40 seconds for request timeouts.

@sciolist
Copy link
Author

sciolist commented Apr 3, 2019

@troyanskiy 320000 seconds is indeed far too long for any real use case, there's no good reason for it to be that high.

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