Support for periodic polling of a JSON REST resource.
Authentication is passed in the Authorization
header, as bearer <token>
.
The response is expected to contain an Etag
header field, which represents the current state of the resource, and is passed back to the server with subsequent requests.
This mechanism allows efficient polling of the server for changes, and can be a workaround for rate-limiting (where requests that didn't pick up any change in state don't count towards the rate limit).
When a request is sent, it is passed a ProcessorGroup
which contains a list of Processor
objects.
When a response comes back, it is matched against each Processor
in turn, matching against the HTTP status code. If a processor supports the code, it is given a chance to decode the response.
If a processor fails to decode the response (throws an error), matching is continued unless the list of processors is exhausted. The first successful match ends this process. If all processors are exhausted without success, then the unprocessed
method of the ProcessorGroup
is called; this can be used for catch-all error handling.
This simple example polls the endpoint https://some.endpoint/v1/
for the resource some/rest/resource
.
When something has changed, a JSON response will be sent back. If the HTTP status code is one that we expect, we will decode the JSON into a Swift object, and call one of our Processor objects with it.
/// if the response is 200, the server will send us an item
struct ItemProcessor: Processor {
struct Item: Decodable {
let name: String
}
let codes = [200]
func process(_ item: Item, response: HTTPURLResponse, in session: Session) -> RepeatStatus {
print("Received item \(item.name)")
return .inherited
}
}
/// if the response is 400, the server will send us an error
struct ErrorProcessor: Processor {
struct Error: Decodable {
let error: String
}
let codes = [400]
func process(_ payload: Error, response: HTTPURLResponse, in session: Session) -> RepeatStatus {
print("Something went wrong: \(payload.error)")
return .inherited
}
}
// make a session for the service we're targetting, supplying the authorization token
let session = Session(base: URL(string: "https://some.endpoint/v1/")!, token: "<api-token>")
// schedule polling of some REST resource
session.poll(target: Resource("some/rest/request"), processors: [ItemProcessor(), ErrorProcessor()], repeatingEvery: 1.0)
// the endpoint will be queried repeatedly by the session
// when an expected response comes back, the response will be decoded and one of our processor objects will be called to process it
RunLoop.main.run()
The swift-tools-version
requirement is set to Swift 5, as the Foundation Networking API isn't quite right on Linux prior to 5.3.
Strictly speaking the code works with Swift 5.2 on Apple platforms, though it requires a fairly modern SDK.
This is a generalisation of some code I built to access the Github API, and is used by Octoid which is a more general Github library.
I split out the JSONSession functionality because I imagined that other servers may use the same mechanism. This may be an incorrect assumption, and/or this code may need to be generalised further to work with other servers. If so, let me know via an issue.