IBM® Toolkit for Swift on z/OS® is a modern toolkit and programming language that focuses on 4 key design principles: safety, performance, modern software design, and language conciseness.

IBM® Toolkit for Swift on z/OS® include IBM Kitura, a free and open-source web framework written in Swift that is an HTTP server and web framework for writing Swift server applications.

This tutorial will demonstrate how to expose z/OS assets (IMS) via RESTful API services using IBM Kitura’s web server running on z/OS.
Figures 1 and 2 illustrate the scenario. We will be querying IBM® z/OS® Connect RESTful APIs which will perform an IMS phonebook transaction. The response from IBM® z/OS® Connect comes in the form of JSON data. We will then manipulate the JSON data from z/OS Connect and send an HTTP response with our own generated JSON data via IBM Kitura.


Figure 1: Diagram of REST API consumer requesting JSON from Swift Back-end Service

While this tutorial leverages IBM® z/OS® Connect Enterprise Edition to access z/OS data, you can replace the back-end logic with direct calls to your z/OS asset. We will be using IBM® z/OS® Connect Enterprise Edition because it provides a single, common way to unleash your existing z/OS assets on IBM in the API economy. As this example will be running on z/OS, the co-location of transactions and assets on z/OS will give better response time and security compared to non co-located services.


Figure 2: RESTFul API with Swift on z/OS

Step 1: Download and Install IBM® Toolkit for Swift on z/OS®

Download the free IBM® Toolkit for Swift on z/OS® Community Edition here: Download Swift on z/OS
Follow the Knowledge Centre Documentation installation instructions to install Swift.

Step 2: Download and Install IBM® Toolkit for Swift on z/OS®

Create your project directory and change your current working directory to it. In this tutorial, the sample project will be called MyProject.


mkdir MyProject
cd MyProject

Create a Swift Package using the swift package subcommand as follows:

swift package init --type=executable

This command will create the directory structure for a Swift Package as follows:

Creating executable package: MyProject
Creating Package.swift
Creating README.md
Creating .gitignore
Creating Sources/
Creating Sources/MyProject/main.swift
Creating Tests/


The main source file for our project resides in Sources/MyProject/main.swift. The Swift Package manifest file, which describes your package and its dependencies, is created under Package.swift. We will modify both files in this article.

Step 3: Modifying the Swift Package Manifest


Let’s first examine the Package.swift manifest file:

// swift-tools-version:4.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
   name: "MyProject",
   dependencies: [
       // Dependencies declare other packages that this package depends on.
       // .package(url: /* package url */, from: "1.0.0"),
   ],
   targets: [
       // Targets are the basic building blocks of a package. A target can define a module or a test suite.
       // Targets can depend on other targets in this package, and on products in packages which this package depends on.
       .target(
           name: "MyProject",
           dependencies: []),
   ]
)

Since our project will depend on IBM Kitura, as well as HeliumLogger, LoggerAPI and SwiftyJSON, we need to add them as dependencies to the Package manifest file as follows:

// swift-tools-version:4.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription
import Foundation

guard let value = ProcessInfo.processInfo.environment["KITURA_ROOT"] else {  // Added
   fatalError("KITURA_ROOT must be present")                                 // Added
}
                                                                            // Added
let package = Package(
   name: "MyProject",
   products: [
       // Products define the executables and libraries produced by a package, and make them visible to other packages.
       .executable(name: "MyProject", targets: ["MyProject"])
   ],
   dependencies: [
       // Dependencies declare other packages that this package depends on.
       .package(url: "\(value)/Kitura", .branch("master") ),       // Added
       .package(url: "\(value)/HeliumLogger", .branch("master") ), // Added
       .package(url: "\(value)/LoggerAPI", .branch("master") ),    // Added
       .package(url: "\(value)/SwiftyJSON", .branch("master") )    // Added
   ],
   targets: [
       // Targets are the basic building blocks of a package. A target can define a module or a test suite.
       .target(
           name: "MyProject",
           dependencies: ["Kitura", "HeliumLogger", "LoggerAPI", "SwiftyJSON"]), // Added
       .testTarget(
           name: "MyProjectTests",
           dependencies: ["MyProject"]),
   ]
)


The lines added or modified are marked with the comment // Added. Since IBM Kitura and the other Kitura dependencies are shipped with IBM® Toolkit for Swift on z/OS®, we need to define the root location to these packages via the environment variable KITURA_ROOT.

export KITURA_ROOT="$(path_to_swift_install)/examples/kitura"

The 3 project dependencies are added to the dependencies array in the Package.swift file. 


Step 4: Modifying the main source file

The main source file is located in Sources/MyProject/main.swift. Since we will be using IBM Kitura’s web framework, we will be using the basic structure of an IBM Kitura program. For more information on IBM Kitura, visit https://www.kitura.io/.
Load the main.swift file in an editor and delete all of the current content.

First, add all the package imports that we will be using in this sample:
import Kitura
import HeliumLogger
import LoggerAPI
import Foundation
import Dispatch
import SwiftyJSON

Next, we will define the Kitura router and create an instance of the logger using the HeliumLogger class. The LoggerAPI package implements the Log class which does nothing unless you attach an instance of a logger to it. Hence, we attach a new instance of HeliumLogger so that the calls to the log method within the rest of Kitura will perform a logging function.

let router = Router() // Create IBM Kitura Router Instance
Log.logger = HeliumLogger() // Attach HeliumLogger instance


At this point, we can define a route point. We will be allowing GET requests on a given name. For example, the url myzosserver.com/person/DUCK will set a GET request to /person/:name?, where DUCK is the value of the name parameter.

router.get("/person/:name?") {
   request, response, next in

   // If a name is specified in the URL
   if let name  = request.parameters["name"] {

                   // Block until we completed the load action
       let semaphore = DispatchSemaphore(value: 0)
         completeLoadAction(lastname: name) { json in
         // Tell the browser that we are sending JSON
         response.headers["Content-Type"] = "application/json; charset=utf-8"
         response.send(json) // Send HTTP response with JSON string
         semaphore.signal()
       }
       _ = semaphore.wait(timeout: .distantFuture)
   }
   next() // Proceed to the next event
}


IBM Kitura routes utilize a Swift callback. In this case the callback is defined as a closure. We inspect the name parameter and if provided, perform a call to completeLoadAction. The completeLoadAction function will perform all the backend processing (query z/OS Connect for data) and return with a JSON string. We then send an HTTP response with the JSON string back to the client via response.send(json).

The completeLoadAction function is defined below:

// A dictionary to look up location based on zip code
let zipCodeDictionary = [ "10012" : "New York", "33019" : "Hollywood", "32836", "Orlando, Florida"]
var dataTask: URLSessionDataTask?

func completeLoadAction(lastname: String, completion: @escaping (String) -> ()) {
   // Define your zos connect RESTful API here
   let urlString = "http://myzosconnectinstance.com/phonebook/contact/\(lastname)"
   let url = URL(string: urlString)!
   let getRequest = URLRequest(url: url)
   let sessionConfig = URLSessionConfiguration.default
   sessionConfig.timeoutIntervalForRequest = 8.0
   dataTask?.cancel()
   let session = URLSession(configuration: sessionConfig, delegate: nil, delegateQueue: nil)
   dataTask = session.dataTask(with: getRequest) {
       data, response, error in
         guard let data = data, error == nil else { // check for fundamental networking error
            completion("") // or return an error code
            return
         }


       // Parse the JSON output
       guard let json = try? JSON(data: data) else {
           completion("") // or return an error code
           return
       }

      // Generate a JSON object to respond with
      var user: JSON = ["First_Name" : json["OUTPUT_AREA"]["OUT_FIRST_NAME"].stringValue,
                        "Last_Name": json["OUTPUT_AREA"]["OUT_LAST_NAME"].stringValue,
                       "Postal_Code": json["OUTPUT_AREA"]["OUT_ZIP_CODE"].stringValue]
       user["Location"].string = "United States"
       if let val = zipCodeDictionary[json["OUTPUT_AREA"]["OUT_ZIP_CODE"].stringValue] {
           user["Location"].string  = val;
       }
       if let string = user.rawString() {
         completion(string)
       }
       completion("")
}
  dataTask?.resume()
}


In the above implementation of completeLoadAction, we perform a URL request to the z/OS connect RESTful API (this should be modified to your z/OS Connect URL). We then parse the data into a JSON object using SwiftyJSON’s JSON class. We then find the location using our dictionary lookup based on the postal/zip code and append the JSON location field to it. Finally, we return our own JSON object, which contains the last name, first name, zip code and location.

Finally, add the calls to the Kitura object to run the http server on port 52000:

Kitura.addHTTPServer(onPort: 52000, with: router)
Kitura.run()

Step 5: Building the Project

To build and run the Swift Project MyProject, we run:

swift run –v

Next, load up your browser and point your URL to http://myzosserver.com:52000/person/DUCK

Figure 3: JSON browser output from client request

Step 6: Adding a secure SSL connection

IBM Kitura supports SSL connections. Use openssl to create a cert.pem and key.pem file and place them on your z/OS server. Modify the main.swift source file as follows:

let myCertPath = "/tmp/cert.pem" // Modify to your location
let myKeyPath = "/tmp/key.pem"  // Modify to your location
let mySSLConfig =  SSLConfig(withCACertificateDirectory: nil, usingCertificateFile: myCertPath,withKeyFile: myKeyPath,  usingSelfSignedCerts: true)
Modify the addHTTPServer to add the mySSLConfig argument as follows:
Kitura.addHTTPServer(onPort: 52000, with: router, withSSL: mySSLConfig)

Conclusion

We were able to use IBM® Toolkit for Swift on z/OS® to successfully launch a web server that calls a z/OS Connect RESTful API service which performs an IMS phonebook transaction.

The entire source for this tutorial is provided with the IBM Toolkit for Swift on z/OS package.

Resources

Join The Discussion

Your email address will not be published. Required fields are marked *