Monthly Archives: February 2022

Installing Erlang on Ubuntu 20.x

The steps below are taken from the sites listed in the reference, recreating here for my reference.

Installing

sudo apt update
sudo apt install software-properties-common apt-transport-https
wget -O- https://packages.erlang-solutions.com/ubuntu/erlang_solutions.asc | sudo apt-key add -
echo "deb https://packages.erlang-solutions.com/ubuntu focal contrib" | sudo tee /etc/apt/sources.list.d/erlang.list
sudo apt update
sudo apt install erlang

Checking it all worked

Let’s check if everything worked. You can just run

erl

from your terminal OR let’s write the good ol’ hello world by create helloworld.erl and add the following code

-module(helloworld). 
-export([start/0]). 
start() -> io:fwrite("Hello World\n").

Run this using

erlc helloworld.erl
// then
erl -noshell -s helloworld start -s init stop

If all went well you’ll see Hello World written to standard io.

To add the last bit of the How to Install Erlang in Ubuntu post, to remove Erlang run

sudo apt remove erlang
sudo apt autoremove

References

How To Install Erlang on Ubuntu 22.04|20.04|18.04
How to Install Erlang in Ubuntu

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)!;

Eureka in Docker

I’ve actually covered this in a previous post, but because it was part of Eureka Server (using Steeltoe) revisited. It didn’t quite stand out when I was searching for it, so let’s recreate here in it’s own post…

Pull the docker image using

docker pull steeltoeoss/eureka-server

Now to run it

docker run --publish 8761:8761 steeltoeoss/eureka-server

This will now be available locally or remotely on port 8761, i.e.

	
http://locahost:8761

Creating a console app. in Swift (on Linux)

The first thing we need to do is use the Swift CLI to generate the project etc. so run

swift package init --type executable

This will generate Sources folder, Tests, the Package.swift and even a README.md and .gitignore.

Now in the previous post we created a simple hello world Vapor REST server, so let’s write the code to call that from our console app.

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

let session = URLSession.shared
let url = URL(string: "http://192.168.1.1:8080")

let semaphore = DispatchSemaphore(value: 0)

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

semaphore.wait()

Let’s break down this code…

The first bit of interest is the import. We need FoundationNetworking for the URLSession.shared within the Linux implementation (if I understand correctly) but not in the Mac OS version (I need to verify that). To allow us to only import FoundationNetworking if required, we use

#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

As you’ve probably surmised URLSession is a shared resource, hence the shared singleton property.

Next we need the URL, which is pretty explanatory. Now the URLSession calls are asynchronous, hence if we just let this code run without the semaphore we’ll exit the application before the response from the web service returns.

The resume method is required to start the task (seems the wrong name to me, but there you go). See dataTask.

Finally we wait on the signal from the semaphore. Within the actual dataTask we’re just going to assume there’s data and no error (ofcourse in a real world application you’ll want to add a guard etc. to check for such things and also check the optionals actual have some value before unwrapping).

In this simple example we’ll simply assuming all worked, unwrap the data and print it to the console. Ofcourse the data will be encoded, so we’re need to create a string from the data and the required encoding as per the String.init.

Now just run the command

swift run

If all went well you’ll see the root level response from the web server code we wrote in the last post.

Getting started with Vapor

Vapor is a web application framework written in Swift.

Installing on Linux

As I’m more likely to be using a Linux server for my web application, that’s what we’ll use for this post. So following the Install on Linux instructione, I went the route of installing the toolbox for the toolbox repo (instructions recreated below)

git clone https://github.com/vapor/toolbox.git
cd toolbox
git checkout <desired version>
make install

This will build and install vapor.

Generating an application

Vapor will generate an application for us using

vapor new hello-vapor -n

(obviously replace hello-vapor with your application name).

As we’re using Linux, we’ll cd into the hello-vapor application folder and then run

swift run

This will run, by default on localhost:8080 which is not great if you want to access remotely, so let’s instead start the application using

swift run Run --hostname 0.0.0.0 --port 8080

Now if you connect to the server’s ip address, port 8080 you should see the default return “It works!” if you use http://localhost:8080/hello you’ll see “Hello, world!” returned.

If you take a look at the source generated by Vapor and check the folder Sources/App/routes.swift you’ll see code like this

import Vapor

func routes(_ app: Application) throws {
    app.get { req in
        return "It works!"
    }

    app.get("hello") { req -> String in
        return "Hello, world!"
    }
}

As you can see this is routing the /hello GET method to return “Hello, world!”.

Using HTTP methods

As you can see from the previous code, app.get denotes the method is a GET method, so let’s simply write an example of each of the HTTP methods, so change your routes.swift file to add the following

app.get("method") { req -> String in
   return "\(req)"
}

app.post("method") { req -> String in
   return "\(req)"
}

app.put("method") { req -> String in
   return "\(req)"
}

app.patch("method") { req -> String in
   return "\(req)"
}

app.delete("method") { req -> String in
   return "\(req)"
}

This doesn’t cover all HTTP methods, but we can use the alternate syntax to the above, which looks like this

app.on(.GET, "method") { req -> String in
   return "\(req)"
}

and simply replace .GET with each of the HTTP methods, such as .CONNECT, .OPTIONS, etc.

Now let’s tests these – if we simply use CURL to execute each method, i.e.

curl -X GET localhost:8080/method
curl -X POST localhost:8080/method
curl -X PUT localhost:8080/method
curl -X PATCH localhost:8080/method
curl -X DELETE localhost:8080/method

Parameters and Query Parameters

We’re also likely to need to handle parameters, so for example /method/aparam

app.get("method", ":param") { req -> String in
   let param = req.parameters.get("param")!        
   return "\(param) --> \(req)"
}

The : prefixing the param token is indicates the parameter is dynamic and hence we need to use req.parameters.get(“param”)! to get the value from the param.

Finally for this post, what about if we want to pass query parameters, for example http://192.168.0.88:8080/method?firstName=Scooby&lastName=Doo

We can declare a struct to handle our expected query parameters like this

struct Person: Content {
    var firstName: String?
    var lastName: String?
}

and now change our GET method to decode the query parameters into this struct, for example

app.get("method") { req -> String in
   let person = try req.query.decode(Person.self)
   return "Firstname: \(person.firstName!), Lastname \(person.lastName!)"
}

There’s a whole lot more functionality, as you’d expect, including streaming, validation etc. take a look at the Vapor Docs.

Publishing my first Rust library to crates.io

As part of my end of 2021 project, I rewrote my F# units of measurement converters to a format which allowed me to code generate a bunch of libraries in different languages, currently including the original F#, C# (both idiomatic to their language), Java, Python, Swift, TypeScript and Rust.

I’ve got the F# and C# ones available on nuget and the TypeScript on npmjs, I wanted to try to get the Rust library onto crates.io.

How do we do this?

  • First off, check if your package name is available on crates.io, if it is then you’ll need to change your name. Rust creates do not support namespaces so you cannot reuse an existing name but prefix with your company or whatever
  • Load up https://crates.io/ and create an account, I think using GitHub to login was the only option, so that’s what I did
  • Ensure you have an email within your profile page and that it’s verified
  • You can go to the API Tokens page and create a new token and a name for the token – you’ll need to copy the API token for the publish step (and keep it for further publishes unless you wish to revoke and regenerate – obviously not too useful in CI. If you set the environment variable CARGO_REGISTRY_TOKEN to the token then cargo will use it from there.
  • Ensure your code is committed (if you’re using source control) or you can always specify the –allow-dirty option if you prefer not to commit changes
  • Ensure you lib.rs has the correct version for your library setup. Semantic versioning is expected, hence you can use -alpha or the likes on your version
  • Now run
    cargo login abcdefghijklmnopqrstuvwxyz
    

    replacing abcdefghijklmnopqrstuvwxyz with your API token

  • Next run
    cargo publish
    

If all the above steps went well you can now search crates.io for your library.

SwiftUI – why is my Mac window not resizable?

As a newbie to SwiftUI, I was surprised to find my simple little sample application would not resize. If you’re used to WPF, WinForms and even other non-Windows user interface libraries, you’ll notice by default the windows are usually resizable. So what’s the deal with SwiftUI

So my code looked like this

var body: some View {
   VStack {
      Text(viewModel.title)
         .padding()
      TextField("Enter", text: $viewModel.title)
   }
   .padding()
   .frame(minWidth: 500, minHeight: 300)
}

Simple enough, display a text label and input text field.

So what’s the problem, why is the Man window not resizable?

Spacer

The Spacer is key here. If we change the code to the following, the it all starts to work as I wanted

var body: some View {
   VStack {
      Text(viewModel.title)
         .padding()
      TextField("Enter", text: $viewModel.title)
      Spacer()
   }
   .padding()
   .frame(minWidth: 500, minHeight: 300)
}

The Spacer expands to fill space.

If you have a Spacer within a VStack the spacer fills space on the vertical and if on an HStack it fills horizontally. But the above code will expand on both the vertical and horizontal. In this case the TextField seems to combine with the Spacer to allow both the axis to expand.