So I have an app that makes frequent requests to various endpoints on our API, and every request pretty much has the same custom headers sent with it. I'd like to know if there is a way to globally set custom header using NSURLSessionConfiguration, and if so...what is the syntax in Swift and where would I put it? AppDelegate? I've done some searching and can't seem to find a good example of this. Is it a bad practice? Not doable?
EDIT:
I'm using Alamofire for request/response, so I need something that sets them globally so that that library (and others that happen to use NSURLSession) will send the headers along by default.
We have this documented right in the README.
var defaultHeaders = Alamofire.Manager.sharedInstance.session.configuration.HTTPAdditionalHeaders ?? [:]
defaultHeaders["DNT"] = "1 (Do Not Track Enabled)"
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.HTTPAdditionalHeaders = defaultHeaders
let manager = Alamofire.Manager(configuration: configuration)
Then you need to use the new manager instead of the global Alamofire singleton.
manager.request(.GET, "https://httpbin.org/get")
.responseJSON { _, _, result in
debugPrint(result)
}
This will attach the DNT header to every request that is sent through this manager instance.
Each Manager instance has its own internal NSURLSession which also has its own configuration. Therefore, this override only works for this Manager instance. If you need these headers on a different Manager instance, you'll have to set it up the same way.
Related
I am trying to integrate SSL public key pinning in Alamofire swift 5, but I found ServerTrustPolicyManager which is deprecated. Please help me to integrate. Thanks.
To integrate SSL public key pinning you first have to add your SSL certificate in your project's target by dragging and dropping it.
To test if your certificate is in the correct format you can try to get the value from publicKeys parameter of the AlamofireExtension in your main Bundle, like this:
print("Bundle public keys: \(Bundle.main.af.publicKeys)")
If that array have at least one element, then you are ready. If it does not, then try importing your SSL certificate to your Mac's Keychain, then export it as .cer and then add it to your project's target. (this should work)
To check if the public key of the SSL certificate is the one that you import in your project you can use the Alamofire's ServerTrustManager with a PublicKeysTrustEvaluator instance, when you create your Session:
let evaluators: [String: ServerTrustEvaluating] = [
"your.domain.com": PublicKeysTrustEvaluator()
]
let serverTrustManager = ServerTrustManager(evaluators: evaluators)
let session = Session(serverTrustManager: serverTrustManager)
Make sure that in the evaluators dictionary, the key ("your.domain.com" in the code above) is your servers domain and if you don't want for Alamofire to perform the default validation and/or validate the host you can pass false to those parameters in PublicKeysTrustEvaluator's initializer:
let evaluators: [String: ServerTrustEvaluating] = [
"your.domain.com": PublicKeysTrustEvaluator(
performDefaultValidation: false,
validateHost: false
)
]
let serverTrustManager = ServerTrustManager(evaluators: evaluators)
let session = Session(serverTrustManager: serverTrustManager)
Then you have to use this Session instance to make any request in your domain, like this:
let url = "https://your.domain.com/path/to/api"
session.request(url, method: .post, parameters: parameters).responseDecodable { response in
}
As #JonShier pointed out in the comments: You need to keep your Session alive beyond the declaring scope. Usually this is done through a single or other outside reference.
I know that in GRAILS/GROOVY
def content=urlrestservicestring.toURL().getBytes(requestProperties: ['User-Accepted': username])
is a short form to have all the byte content (for example for PDF donwload), but I don't know all the request properties available for URL for richer connections, for example for POST method (this is a GET call) with payload in json. Is it possible? In which way?
It looks like per requestProperties you can set request headers only which might help for simple cases.
On the other hand if you want to do something more complex, like a POST, you have to use a propper HTTP-client.
In Groovy there's an idiomatic HTTPBuilder which is very straight-forward and easy to use, or Grails own RESTBuilder
UPDATE:
Using HTTPBuilder the download could look like:
import static groovyx.net.http.HttpBuilder.configure
configure {
request.uri = "http://example.org/download"
request.contentType = 'application/json'
}.post {
request.headers.username = 'Scarpanti'
request.body = [ some:'json' ]
Download.toStream delegate, response.outputStream // from grails
// or
// Download.toStream delegate, file
}
see also ref-doc
One of my Vapor app endpoints needs to be able to receive arbitrary JSON and output it to the logs. Once I have the logs, I can go back through and set up Codables structs and do the typical Vapor workflow.
In your route handler do print("\(req)") and you'll see the data
Doing this requires solving two problems that Vapor apps don't normally contend with:
Where to get the HTTP body (and how to decode it)?
How to return a future outside the context of the usual helpers?
The simplest solution I found gets the body from req.http.body.data, converts the data to JSON using JSONSerialization.jsonObject(with:options), and returns a Future using req.eventLoop.newSucceededFuture:
router.put("printanyjson") { req -> Future<HTTPStatus> in
if let data = req.http.body.data {
if let json = try? JSONSerialization.jsonObject(with: data, options: []) {
print("\(json)")
}
}
return req.eventLoop.newSucceededFuture(result: .ok)
}
Note #1: This solution does not work if body is streaming. I'd like to see a solution that incorporates this idea.
Note #2: It's also possible to make recursive Codables that can decode any structure, allowing you to stay within the rails of typical Vapor usage. I'd like to see a solution that incorporates this idea.
I am trying to create websocket server and client in my iOS app, which i successfully managed to do with the help of sample implementation here. (https://github.com/apple/swift-nio/tree/master/Sources/NIOWebSocketServer) - so current working situation is, i run the websocket server when app launches and then I load the client in a webview which can connect to it.
Now my problem is I want my server to secured websocket server (Basically connect to the websocket server from a HTTPS html page)
I am new to network programming and Swift-nio documentation is lacking to say the least. As far as I understand I could use (https://github.com/apple/swift-nio-transport-services)
I found this thread which is exactly what I need - https://github.com/apple/swift-nio-transport-services/issues/39 - I could disable the TLS authentication as I dont care in my usecase as long as I could get the websocket connected.
So my question is how to I extend my client (https://github.com/apple/swift-nio/tree/master/Sources/NIOWebSocketClient) and server (https://github.com/apple/swift-nio/tree/master/Sources/NIOWebSocketServer) to use swift-nio-transport-service.
I could add the NIOSSLContext and stuff but I think I need to add the EventLoopGroup and new bootstrap methods. I know the answers is right there.... but I just cannot seem to pinpoint it.
Any pointer would be appreciated.
Thanks.
To translate a simple NIO Server to a NIOTransportServices one, you need to make the following changes:
Add a dependency on NIOTransportServices to your server.
Change MultiThreadedEventLoopGroup to NIOTSEventLoopGroup.
Change ClientBootstrap to NIOTSConnectionBootstrap.
Change ServerBootstrap to NIOTSListenerBootstrap.
Build and run your code.
Some ChannelOptions don’t work in NIOTransportServices, but most do: the easiest way to confirm that things are behaving properly is to quickly test the common flow.
This doesn’t add any extra functionality to your application, but it does give you the same functionality using the iOS APIs.
To add TLS to either NIOTSConnectionBootstrap or NIOTSListenerBootstrap, you use the .tlsOptions function. For example:
NIOTSListenerBootstrap(group: group)
.tlsOptions(myTLSOptions())
Configuring a NWProtocolTLS.Options is a somewhat tricky thing to do. You need to obtain a SecIdentity, which requires interacting with the keychain. Quinn has discussed this somewhat here.
Once you have a SecIdentity, you can use it like so:
func myTLSOptions() -> NWProtocolTLS.Options {
let options = NWProtocolTLS.Options()
let yourSecIdentity = // you have to implement something here
sec_protocol_options_set_local_identity(options.securityProtocolOptions, sec_identity_create(yourSecIdentity)
return options
}
Once you have that code written, everything should go smoothly!
As an extension, if you wanted to secure a NIO server on Linux, you can do so using swift-nio-ssl. This has separate configuration as the keychain APIs are not available, and so you do a lot more loading of keys and certificates from files.
I needed a secure websocket without using SecIdentity or NIOTransportServices, so based on #Lukasa's hint about swift-nio-ssl I cobbled together an example that appears to work correctly.
I dunno if it's correct, but I'm putting it here in case someone else can benefit. Error-handling and aborting when the try's fail is left out for brevity.
let configuration = TLSConfiguration.forServer(certificateChain: try! NIOSSLCertificate.fromPEMFile("/path/to/your/tlsCert.pem").map { .certificate($0) }, privateKey: .file("/path/to/your/tlsKey.pem"))
let sslContext = try! NIOSSLContext(configuration: configuration)
let upgradePipelineHandler: (Channel, HTTPRequestHead) -> EventLoopFuture<Void> = { channel, req in
WebSocket.server(on: channel) { ws in
ws.send("You have connected to WebSocket")
ws.onText { ws, string in
print("Received text: \(string)")
}
ws.onBinary { ws, buffer in
// We don't accept any Binary data
}
ws.onClose.whenSuccess { value in
print("onClose")
}
}
}
self.eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 2)
let port: Int = 5759
let promise = self.eventLoopGroup!.next().makePromise(of: String.self)
_ = try? ServerBootstrap(group: self.eventLoopGroup!)
// Specify backlog and enable SO_REUSEADDR for the server itself
.serverChannelOption(ChannelOptions.backlog, value: 256)
.serverChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
.childChannelInitializer { channel in
let handler = NIOSSLServerHandler(context: sslContext)
_ = channel.pipeline.addHandler(handler)
let webSocket = NIOWebSocketServerUpgrader(
shouldUpgrade: { channel, req in
return channel.eventLoop.makeSucceededFuture([:])
},
upgradePipelineHandler: upgradePipelineHandler
)
return channel.pipeline.configureHTTPServerPipeline(
withServerUpgrade: (
upgraders: [webSocket],
completionHandler: { ctx in
// complete
})
)
}.bind(host: "0.0.0.0", port: port).wait()
_ = try! promise.futureResult.wait()
try! server.close(mode: .all).wait()
How does one change user agent strings in http requests made in R? And how do I figure out what my current user agent string looks like?
Thanks in advance.
options("HTTPUserAgent") or getOption("HTTPUserAgent") prints your current settings, and options(HTTPUserAgent="My settings") is the way to change it.
To temporary change this option use: withr::with_options:
withr::with_options(list(HTTPUserAgent="My settings"), download.file(..something..))
or Droplet answer if you use download.file.
The solution using options() in the accepted answer will change the setting globally for the whole session (unless you change it back).
To change the User-Agent temporarily in a request made by download.file(), you need to use the headers argument:
download.file("https://httpbin.org/user-agent",
destfile = "test.txt",
headers = c("User-Agent" = "My Custom User Agent"))
Since R 4.0.0, you can also use this argument in available.packages() and install.packages() and it will be forwarded to download.file().