Interacting with Eureka from Swift 5

I wanted to get my little Swift console application (developed in the post Creating a console app. in Swift (on Linux)) working with Eureka.

So in the last post I outlined the simple steps to get Eureka up and running in docker. Now let’s combine these posts along with the information from my the post Interacting with Eureka via it’s REST operations and create ourselves a Swift Eureka client.

Before I get started, I have not found the generation of JSON from types or for that matter generating the Dictionary required for JSONSerialization as intuitive as C# (for example). I’m not sure if there’s a better way to do these things, but this code works so it’ll do for now. But if you know a better way please use it.

To start with we’re just going to manually generate our JSON string to match the POST request that we need to tell EUREKA that our application is starting

let json: String = "{" +
  "\"instance\": {" +
    "\"hostName\": \"myhost\"," +
    "\"app\": \"SWIFT-EUREKA\"," +
    "\"vipAddress\": \"myservice\"," +
    "\"secureVipAddress\": \"myservice\"," +
    "\"ipAddr\": \"10.0.0.10\"," +
    "\"status\": \"STARTING\"," +
    "\"port\": {\"$\": \"8080\", \"@enabled\": \"true\"}," +
    "\"securePort\": {\"$\": \"8443\", \"@enabled\": \"true\"}," +
    "\"healthCheckUrl\": \"http://myservice:8080/healthcheck\"," +
    "\"statusPageUrl\": \"http://myservice:8080/status\"," +
    "\"homePageUrl\": \"http://myservice:8080\"," +
    "\"dataCenterInfo\": {" +
      "\"@class\": \"com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo\", " +
      "\"name\": \"MyOwn\"" +
    "}" +
  "}" +
"}"

Hopefully this is fairly obvious (even with all the escaping characters) what we’re doing, but to use this in JSONSerialization.data method is seems we need to convert this string to a Dictionary or Array, so here’s an extension method to convert to a dictionary

extension String {
  func toDictionary() -> [String:AnyObject]? {
    if let data = self.data(using: .utf8) {
      return try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String:AnyObject]
    }
    return nil
  } 
}

There’s zero error handling here – I leave it to the reader to add that.

So using the extension method toDictionary we’ll convert our JSON string into a dictionary which is then passed into the JSONSerialization.data to create a Data object, i.e.

let data = json.toDictionary()
let httpBody = try JSONSerialization.data(withJSONObject: data!, options: [])

Now we’ll create the URL and URLRequest to call the Eureka service

let url = URL(string: "http://192.168.1.1:8761/eureka/apps/swift-eureka")
var request = URLRequest(url: url!)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.httpBody = httpBody
request.timeoutInterval = 20

The rest of the code is the same as Creating a console app. in Swift (on Linux) but for completeness it’s list below in it’s entirety (don’t forget the extension method toDictionary is in some extension file).

import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

let session = URLSession.shared
let semaphore = DispatchSemaphore(value: 0)

let json: String = "{" +
  "\"instance\": {" +
    "\"hostName\": \"myhost\"," +
    "\"app\": \"SWIFT-EUREKA\"," +
    "\"vipAddress\": \"myservice\"," +
    "\"secureVipAddress\": \"myservice\"," +
    "\"ipAddr\": \"10.0.0.10\"," +
    "\"status\": \"DOWN\"," +
    "\"port\": {\"$\": \"8080\", \"@enabled\": \"true\"}," +
    "\"securePort\": {\"$\": \"8443\", \"@enabled\": \"true\"}," +
    "\"healthCheckUrl\": \"http://myservice:8080/healthcheck\"," +
    "\"statusPageUrl\": \"http://myservice:8080/status\"," +
    "\"homePageUrl\": \"http://myservice:8080\"," +
    "\"dataCenterInfo\": {" +
      "\"@class\": \"com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo\", " +
      "\"name\": \"MyOwn\"" +
    "}" +
  "}" +
"}"

let data = json.toDictionary()
let httpBody = try? JSONSerialization.data(withJSONObject: data!, options: [])

let url = URL(string: "http://192.168.0.88:8761/eureka/apps/swift-eureka")
var request = URLRequest(url: url!)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.httpBody = httpBody
request.timeoutInterval = 20

session.dataTask(with: request) { (data, response, error) in
    let result = String.init(data: data!, encoding: .utf8)
    print(result!)
    semaphore.signal()
}.resume()

semaphore.wait()

At this time I haven’t got a great solution for using types to define our request as the request has some invalid name keys, such as $ and @enabled (again there may be a solution which I’ve not come across yet). To give the reader a starting point, here’s the code I have for turning my EurekaRequest type into a JSON string.

I’ve not included the EurekaRequest so this really just shows a way to convert a type to a JSON string.

let instance = EurekaRequest()
let encoder = JSONEncoder();
// required to stop // becoming \/
encoder.outputFormatting = .withoutEscapingSlashes
let jsonData = try encoder.encode(instance)

// convert to a string
var json = String(data: jsonData, encoding: String.Encoding.utf8)!;